diff options
author | zea <zea@chromium.org> | 2015-06-03 10:51:25 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-06-03 17:52:09 +0000 |
commit | 4a996cdc7a36a71ac511c153375fc6170fea80e6 (patch) | |
tree | d8009a33833db8d801117502eca0e69ec6262503 /third_party | |
parent | 5fc460f2bd3055f9142567a7b6e12d1b9e17de3c (diff) | |
download | chromium_src-4a996cdc7a36a71ac511c153375fc6170fea80e6.zip chromium_src-4a996cdc7a36a71ac511c153375fc6170fea80e6.tar.gz chromium_src-4a996cdc7a36a71ac511c153375fc6170fea80e6.tar.bz2 |
Pull cacheinvalidations code directory into chromium repo.
Code.google.com is being turned down, and cacheinvalidation is still necessary
for Chromium, and depends on base/. Pull it directly into the chromium repo to
preserve it and ensure any changes to base/ that conflict with the
cacheinvalidation code are caught at compile time.
BUG=472898
Review URL: https://codereview.chromium.org/1162033004
Cr-Commit-Position: refs/heads/master@{#332636}
Diffstat (limited to 'third_party')
200 files changed, 36198 insertions, 18 deletions
diff --git a/third_party/cacheinvalidation/DEPS b/third_party/cacheinvalidation/DEPS index 048443d..634093f 100644 --- a/third_party/cacheinvalidation/DEPS +++ b/third_party/cacheinvalidation/DEPS @@ -1,3 +1,4 @@ include_rules = [ + '+base', '+google', -]
\ No newline at end of file +] diff --git a/third_party/cacheinvalidation/README.chromium b/third_party/cacheinvalidation/README.chromium index 03444b9..4259807 100644 --- a/third_party/cacheinvalidation/README.chromium +++ b/third_party/cacheinvalidation/README.chromium @@ -1,24 +1,13 @@ Name: Google Cache Invalidation API Short Name: google-cache-invalidation-api -URL: http://code.google.com/p/google-cache-invalidation-api/ -Version: r341 +URL: n/a +Version: unknown License: Apache 2.0 License File: src/google/cacheinvalidation/COPYING Security Critical: no Description: -This is the API to talk to the Google Cache Invalidation service. - -Local Modifications: -None. - -Note: If you are rolling forward the Cache Invalidation API version, and want to -check if any changes need to be made to cacheinvalidation.gyp, do the following: - - cd src/third_party/cacheinvalidation/src - git remote update - git diff --diff-filter=ACDR --name-only origin/master | grep -v ^java/ - -This should give you a list of relevant files that were added, copied, renamed -or deleted upstream. You will likely need to make appropriate changes to -cacheinvalidation.gyp to keep the build green. +This is the API to talk to the Google Cache Invalidation service, used by the +invalidations consumed by Sync, Enterprise Policy, and other services. It was +previously hosted at http://code.google.com/p/google-cache-invalidation-api/ +but was merged into the Chromium repository at r342. diff --git a/third_party/cacheinvalidation/src/example-app-build/AndroidManifest.xml b/third_party/cacheinvalidation/src/example-app-build/AndroidManifest.xml new file mode 100644 index 0000000..9a2a4db --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/AndroidManifest.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" ?> +<!-- Copyright 2011 Google Inc. All Rights Reserved. --> +<!-- Manifest for AndroidListener sample application. Must be merged with + j/c/g/ipc/invalidation/external/client/contrib:android_listener_manifest. --> +<manifest android:versionCode="1" android:versionName="0.1" package="com.google.ipc.invalidation.examples.android2" xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- *** WARNING *** DO NOT EDIT! THIS IS GENERATED MANIFEST BY MERGE_MANIFEST TOOL. + Merger manifest: + java/com/google/ipc/invalidation/examples/android2/AndroidManifest.xml + Mergee manifests: + blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml + --> + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="14"/> + <!-- Declare and use permission allowing this application to receive GCM + messages. --> + <permission android:name="com.google.ipc.invalidation.examples.android2.permission.C2D_MESSAGE" android:protectionLevel="signature"/> + <uses-permission android:name="com.google.ipc.invalidation.examples.android2.permission.C2D_MESSAGE"/> + <application> + <!-- Configure the listener class for the application --> + <meta-data android:name="ipc.invalidation.ticl.listener_service_class" android:value="com.google.ipc.invalidation.examples.android2.ExampleListener"/> + <!-- To enable background invalidations uncomment the following element: + --> + <!--<meta-data + android:name= + "ipc.invalidation.ticl.background_invalidation_listener_service_class" + android:value= + "com.google.ipc.invalidation.examples.android2.ExampleListener"/>--> + <!-- Example activity --> + <activity android:name="com.google.ipc.invalidation.examples.android2.MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <!-- Ticl listener. --> + <service android:exported="false" android:name="com.google.ipc.invalidation.examples.android2.ExampleListener"> + <intent-filter> + <action android:name="com.google.ipc.invalidation.AUTH_TOKEN_REQUEST"/> + </intent-filter> + </service> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Receiver for scheduler alarms. --> + <receiver android:exported="false" android:name="com.google.ipc.invalidation.external.client.contrib.AndroidListener$AlarmReceiver"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Receiver for scheduler alarms. --> + <receiver android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.AndroidInternalScheduler$AlarmReceiver"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- GCM Broadcast Receiver --> + <receiver android:exported="true" android:name="com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener$GCMReceiver" android:permission="com.google.android.c2dm.permission.SEND"> + <intent-filter> + <action android:name="com.google.android.c2dm.intent.RECEIVE"/> + <action android:name="com.google.android.c2dm.intent.REGISTRATION"/> + <category android:name="com.google.ipc.invalidation.ticl.android2"/> + </intent-filter> + </receiver> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Merged from file: java/com/google/ipc/invalidation/external/client/android2/AndroidManifest.xml --> + <receiver android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageReceiverService$Receiver"> + <intent-filter> + <action android:name="com.google.ipc.invalidation.gcmmplex.EVENT"/> + </intent-filter> + </receiver> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Ticl service. --> + <service android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.TiclService"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Ticl sender. --> + <service android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageSenderService"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- GCM multiplexer --> + <service android:exported="false" android:name="com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener"> + <meta-data android:name="sender_ids" android:value="ipc.invalidation@gmail.com"/> + </service> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Invalidation service multiplexed GCM receiver --> + <service android:enabled="true" android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageReceiverService"/> + </application> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- App receives GCM messages. --> + <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- GCM connects to Google Services. --> + <uses-permission android:name="android.permission.INTERNET"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- GCM requires a Google account. --> + <uses-permission android:name="android.permission.GET_ACCOUNTS"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Merged from file: java/com/google/ipc/invalidation/external/client/android2/AndroidManifest.xml --> + <uses-permission android:name="android.permission.USE_CREDENTIALS"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Keeps the processor from sleeping when a message is received. --> + <uses-permission android:name="android.permission.WAKE_LOCK"/> +</manifest> diff --git a/third_party/cacheinvalidation/src/example-app-build/README b/third_party/cacheinvalidation/src/example-app-build/README new file mode 100644 index 0000000..07cd60e --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/README @@ -0,0 +1,8 @@ +Copyright 2012 Google Inc. All Rights Reserved. + +This directory contains the files required to build the example application for +the Invalidation Client, the source code of which is located under +../src/java/com/google/ipc/invalidation/examples/android2. + +To build the example, first run ./generate_protos.sh. Then, run ant debug or +ant release to build the application. diff --git a/third_party/cacheinvalidation/src/example-app-build/ant.properties b/third_party/cacheinvalidation/src/example-app-build/ant.properties new file mode 100644 index 0000000..b79d939 --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/ant.properties @@ -0,0 +1 @@ +source.dir = ../java:generated-protos:../javaexample diff --git a/third_party/cacheinvalidation/src/example-app-build/build.xml b/third_party/cacheinvalidation/src/example-app-build/build.xml new file mode 100644 index 0000000..4b8f3c0 --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/build.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="MainActivity" default="help"> + + <!-- The local.properties file is created and updated by the 'android' tool. + It contains the path to the SDK. It should *NOT* be checked into + Version Control Systems. --> + <property file="local.properties" /> + + <!-- The ant.properties file can be created by you. It is only edited by the + 'android' tool to add properties to it. + This is the place to change some Ant specific build properties. + Here are some properties you may want to change/update: + + source.dir + The name of the source directory. Default is 'src'. + out.dir + The name of the output directory. Default is 'bin'. + + For other overridable properties, look at the beginning of the rules + files in the SDK, at tools/ant/build.xml + + Properties related to the SDK location or the project target should + be updated using the 'android' tool with the 'update' action. + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. + + --> + <property file="ant.properties" /> + + <!-- The project.properties file is created and updated by the 'android' + tool, as well as ADT. + + This contains project specific properties such as project target, and library + dependencies. Lower level build properties are stored in ant.properties + (or in .classpath for Eclipse projects). + + This file is an integral part of the build system for your + application and should be checked into Version Control Systems. --> + <loadproperties srcFile="project.properties" /> + + <!-- quick check on sdk.dir --> + <fail + message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var" + unless="sdk.dir" + /> + + +<!-- extension targets. Uncomment the ones where you want to do custom work + in between standard targets --> +<!-- + <target name="-pre-build"> + </target> +--> +<!-- + <target name="-pre-build"> + <target name="-pre-compile"> + </target> + + /* This is typically used for code obfuscation. + Compiled code location: ${out.classes.absolute.dir} + If this is not done in place, override ${out.dex.input.absolute.dir} */ + <target name="-post-compile"> + </target> +--> + + <!-- Import the actual build file. + + To customize existing targets, there are two options: + - Customize only one target: + - copy/paste the target into this file, *before* the + <import> task. + - customize it to your needs. + - Customize the whole content of build.xml + - copy/paste the content of the rules files (minus the top node) + into this file, replacing the <import> task. + - customize to your needs. + + *********************** + ****** IMPORTANT ****** + *********************** + In all cases you must update the value of version-tag below to read 'custom' instead of an integer, + in order to avoid having your file be overridden by tools such as "android update project" + --> + <!-- version-tag: 1 --> + <import file="${sdk.dir}/tools/ant/build.xml" /> + +</project> diff --git a/third_party/cacheinvalidation/src/example-app-build/generate_protos.sh b/third_party/cacheinvalidation/src/example-app-build/generate_protos.sh new file mode 100755 index 0000000..0007b9f --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/generate_protos.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# Copyright 2012 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. + +# Tool to output Java classes for the protocol buffers used by the invalidation +# client into the generated-protos/ directory. +TOOL=${PROTOC- `which protoc`} +mkdir -p generated-protos/ +$TOOL --javanano_out=optional_field_style=reftypes:generated-protos/ ../proto/* --proto_path=../proto/ +EXAMPLE_PATH=../javaexample/com/google/ipc/invalidation/examples/android2 +$TOOL --javanano_out=optional_field_style=reftypes:generated-protos/ $EXAMPLE_PATH/example_listener.proto --proto_path=$EXAMPLE_PATH diff --git a/third_party/cacheinvalidation/src/example-app-build/libs/gcm.jar b/third_party/cacheinvalidation/src/example-app-build/libs/gcm.jar Binary files differnew file mode 100644 index 0000000..ac109a8 --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/libs/gcm.jar diff --git a/third_party/cacheinvalidation/src/example-app-build/libs/protobuf-java-2.3.0-nano.jar b/third_party/cacheinvalidation/src/example-app-build/libs/protobuf-java-2.3.0-nano.jar Binary files differnew file mode 100644 index 0000000..b51536a --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/libs/protobuf-java-2.3.0-nano.jar diff --git a/third_party/cacheinvalidation/src/example-app-build/local.properties b/third_party/cacheinvalidation/src/example-app-build/local.properties new file mode 100644 index 0000000..e521a1c --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/local.properties @@ -0,0 +1,10 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked in Version Control Systems, +# as it contains information specific to your local configuration. + +# location of the SDK. This is only used by Ant +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/home/dsmyers/bin/android-sdk-linux diff --git a/third_party/cacheinvalidation/src/example-app-build/proguard.cfg b/third_party/cacheinvalidation/src/example-app-build/proguard.cfg new file mode 100644 index 0000000..9533f06 --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/proguard.cfg @@ -0,0 +1,76 @@ +# +# This file was derived from the Android SDK default configuration in tools/lib/proguard.cfg, +# with changes/additions explicitly commented where made +# +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +# Change: SDK defaults + code/allocation/variable required to disable proguard optimization bug +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +# Change: not needed +#-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native <methods>; +} + +-keepclasseswithmembers class * { + public <init>(android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public <init>(android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} + +# +# All changes below are additions to the Android SDK defaults, generally for the purposes of +# suppressing spurious or inconsequential warnings. +# + +# Suppress duplicate warning for system classes; Blaze is passing android.jar +# to proguard multiple times. +-dontnote android.** +-dontnote java.** +-dontnote javax.** +-dontnote junit.** +-dontnote org.** +-dontnote dalvik.** +-dontnote com.android.internal.** + +# Stop warnings about missing unused classes +-dontwarn com.google.common.annotations.** +-dontwarn com.google.common.base.** +-dontwarn com.google.common.collect.** +-dontnote com.google.common.flags.** +-dontwarn com.google.common.flags.** +-dontwarn com.google.common.util.concurrent.** + +# Ignore missing JDK6 classes +-dontwarn java.** + +# Inverting these produces significant size gains but loses significant debug info +-dontobfuscate +#-flattenpackagehierarchy diff --git a/third_party/cacheinvalidation/src/example-app-build/project.properties b/third_party/cacheinvalidation/src/example-app-build/project.properties new file mode 100644 index 0000000..9aa0dfa --- /dev/null +++ b/third_party/cacheinvalidation/src/example-app-build/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=Google Inc.:Google APIs:15 diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/COPYING b/third_party/cacheinvalidation/src/google/cacheinvalidation/COPYING new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/README b/third_party/cacheinvalidation/src/google/cacheinvalidation/README new file mode 100644 index 0000000..a9dd982 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/README @@ -0,0 +1,16 @@ +This directory contains the implementation of the client library for a cache +invalidation service. + +The public (stable) interfaces are those defined under include/: + invalidation-client.h + invalidation-client-factory.h + invalidation-listener.h + system-resources.h + types.h + +In order to compile this library, proper implementations of the interfaces +residing under deps/ must be provided. + +Interfaces and implementations defined under impl/ are subject to change, and +the test/ directory contains test helpers. Please do not depend directly +on anything in these directories. diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/android_channel.proto b/third_party/cacheinvalidation/src/google/cacheinvalidation/android_channel.proto new file mode 100644 index 0000000..1e6a1fb --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/android_channel.proto @@ -0,0 +1,91 @@ +/* + * 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. + */ +// +// The Android delivery service's network endpoint id descriptor. +// This proto is internal to the Android channel. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package ipc.invalidation; + +import "client_protocol.proto"; + +// Defines the valid major versions of the android channel protocol. The +// channel version controls the expected envelope syntax and semantics of +// http and c2dm messages sent between the client and server. +enum MajorVersion { + option allow_alias = true; + + // The initial version of the android channel protocol. Inbound and + // outbound channel packets contained a single binary protocol message only. + INITIAL = 0; + + // Adds batching (multiple protocol messages in a single channel message) + BATCH = 1; + + // The default channel version used by Android clients. Lower major numbers + // will represent earlier versions and higher numbers will represent + // experimental versions that are not yet released. + DEFAULT = 0; + + // The minimum and maximum supported channel major versions. Used to validate + // incoming requests, so update as new versions are added or old versions are + // no longer supported. + MIN_SUPPORTED = 0; + MAX_SUPPORTED = 1; +} + +// An id that specifies how to route a message to a Ticl on an Android device +// via C2DM. +message EndpointId { + // Field 1 was once the ProtocolVersion of this message. + + // The "registration_id" returned when the client registers with c2dm. This + // id is required by c2dm in order to send a message to the device. + optional string c2dm_registration_id = 2; + + // A key identifying a specific client on a device. + optional string client_key = 3; + + // The C2DM sender ID to use to deliver messages to the endpoint. + optional string sender_id = 4 [deprecated = true]; + + // Defines the expected channel version generated by the network endpoint or + // expected in messages sent from the server. + optional Version channel_version = 5; + + // The package name of the Android application that will receive the messages. + // Replaces sender_id. Must be set (unless sender_id is set; in which case it + // must not be set). + optional string package_name = 6; +} + +// A message addressed to a particular Ticl on an Android device. +message AddressedAndroidMessage { + // Client on the device to which the message is destined. + optional string client_key = 1; + + // Message contents (serialized ServerToClientMessage). + optional bytes message = 2; +} + +// A batch of messages addressed to potentially-different Ticls on the same +// Android device. +message AddressedAndroidMessageBatch { + repeated AddressedAndroidMessage addressed_message = 1; +} diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/channel_common.proto b/third_party/cacheinvalidation/src/google/cacheinvalidation/channel_common.proto new file mode 100644 index 0000000..58e73be --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/channel_common.proto @@ -0,0 +1,49 @@ +// 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. +// +// Common utilities used by all channel related protos. +// This is also publicly visible to all channel implementors. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package ipc.invalidation; + +message ChannelMessageEncoding { + // What kind of encoding is used for network_message + enum MessageEncoding { + // Raw proto encoding + PROTOBUF_BINARY_FORMAT = 1; + + // JSPB-encoding: https://sites.google.com/a/google.com/jspblite/Home + PROTOBUF_JSON_FORMAT = 2; + } +} + +message NetworkEndpointId { + enum NetworkAddress { + TEST = 1; // A delivery service for testing + + // Low numbers reserved. + ANDROID = 113; // Android delivery service using c2dm / http. + } + optional NetworkAddress network_address = 1; + optional bytes client_address = 2; + + // Optional. When true, the client is considered offline but the + // client_address is maintained so that the client can potentially be reached. + // When false or undefined, the client is considered online. + optional bool is_offline = 3; +} diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/client.proto b/third_party/cacheinvalidation/src/google/cacheinvalidation/client.proto new file mode 100644 index 0000000..e79b327 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/client.proto @@ -0,0 +1,64 @@ +// 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. + +// Specification of protocol buffers that are used only on the client side. +// +// Note: unless otherwise specified in a comment, all fields in all messages +// are required, even though they are listed as optional. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package ipc.invalidation; + +import "client_protocol.proto"; + +// An object that is serialized and given to clients for acknowledgement +// purposes. +message AckHandleP { + optional InvalidationP invalidation = 1; +} + +// The state persisted at a client so that it can be used after a reboot. +message PersistentTiclState { + // Last token received from the server (required). + optional bytes client_token = 1; + + // Last time a message was sent to the server (optional). Must be a value + // returned by the clock in the Ticl system resources. + optional int64 last_message_send_time_ms = 2 [default = 0]; +} + +// An envelope containing a Ticl's internal state, along with a digest of the +// serialized representation of this state, to ensure its integrity across +// reads and writes to persistent storage. +message PersistentStateBlob { + // The (important parts of the) Ticl's internal state. + optional PersistentTiclState ticl_state = 1; + + // Implementation-specific message authentication code for the Ticl state. + optional bytes authentication_code = 2; +} + +// State of a Ticl RunState. +message RunStateP { + enum State { + NOT_STARTED = 1; + STARTED = 2; + STOPPED = 3; + } + optional State state = 1; +} + diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/client_gateway.proto b/third_party/cacheinvalidation/src/google/cacheinvalidation/client_gateway.proto new file mode 100644 index 0000000..5b12452 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/client_gateway.proto @@ -0,0 +1,38 @@ +// 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. + +// Specification of invalidation gateway internal forwarding messages and +// services. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package ipc.invalidation; + +// The message communicated between gateway and clients. +message ClientGatewayMessage { + // Whether it is client to server or server to client. + optional bool is_client_to_server = 1; + + // Serialized version of the ServiceContext. + optional bytes service_context = 2; + + // Rpc scheduling hash. + optional int64 rpc_scheduling_hash = 3; + + // Payload of the network message (ClientToServerMessage or + // ServerToClientMessage). + optional bytes network_message = 4; +} diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/client_protocol.proto b/third_party/cacheinvalidation/src/google/cacheinvalidation/client_protocol.proto new file mode 100644 index 0000000..285457d --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/client_protocol.proto @@ -0,0 +1,595 @@ +// 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. + +// Specification of protocol buffers and the client-server protocol that are +// used by the clients of the system. +// +// Note: unless otherwise specified in a comment, all fields in all messages +// are required, even though they are listed as optional. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package ipc.invalidation; + +// Here is a high-level overview of the protocol. The protocol is designed in a +// "message-passing" style, i.e., the client (C) or the server (S) can send any +// message SPONTANEOUSLY at any time and both sides have to be prepared for any +// message from the other side. However, even with this style, there are some +// "flows" which are somewhat like requests and replies. + +// 1. Initialization: When a client starts up, it needs a token to alow it to +// perform any other operation with the server. +// C -> S: InitializeMessage +// S -> C: TokenControlMessage +// +// 2. Registration: When a client has to register or unregister for a set of +// objects, the following flow occurs: +// C -> S: RegistrationMessage +// S -> C: RegistrationStatusMessage +// +// 3. Invalidation: The server sends an invalidation and the client sends back +// an ack. +// S -> C InvalidationMessage +// C -> S InvalidationMessage +// +// 4. Registration sync: Once in a while the server may detect that the client +// and server's registration state is out of sync (the server can detect this +// since it gets the client's registration summary in the client's message +// header). In that case, it asks the client some registration information +// and the client sends it to the server. +// S -> C: RegistrationSyncRequestMessage +// C -> S: RegistrationSyncMessage +// +// 5. Information messages: The server can occasionally for client-side +// information such as statistics, etc. The client responds with the +// requested information +// S -> C: InfoRequestMessage +// C -> S: InfoMessage +// +// ------------------------------------------------------------------------ + +// A basic message type used for versioning public proto messages and/or +// types. The two fields are supposed to be used as follows: +// +// * The major version number is changed whenever an incompatible protocol +// change or type has been made. When a message/object with a particular +// major version is received, the receiver needs to either know how to handle +// this version or it needs to drop the message +// +// * The minor version can be changed (say) to document some internal change +// for debugging purposes. When a message is received by a receiver, it MUST +// ignore the minor version number for making any protocol/type +// decisions. I.e., the minor version number is for debugging purposes only. +// +// Versioning is used in various places - for entities that are part of a +// protocol (e.g., message requests), for various client implementations, and +// for other data types that change independently of the protocol (e.g., +// session tokens). For each versioned entity, we define a specific message +// type to encapsulate the version of that entity (e.g., ProtocolVersion, +// ClientVersion, etc.). +message Version { + optional int32 major_version = 1; + optional int32 minor_version = 2; +} + +// Message included in all client <-> server messages to indicate the version +// of the protocol in use by the sender. +message ProtocolVersion { + optional Version version = 1; +} + +// Defines a specific version of the client library (for information purposes +// only) May not be used to make decisions at the server (use ProtocolVersion +// instead). +message ClientVersion { + + // A client-specific version number. + optional Version version = 1; + + // All fields below are for informational/debugging/monitoring purposes only. + // No critical code decision is supposed to be made using them. + + // Information about the client operating system/platform, e.g., Windows, + // ChromeOS. + optional string platform = 2; + + // Language used for the library. + optional string language = 3; + + // Extra information about the client (e.g., application name). + optional string application_info = 4; +} + +// Message indicating the result of an operation. +message StatusP { + + // Whether operation is successful or not + enum Code { + SUCCESS = 1; + TRANSIENT_FAILURE = 2; + PERMANENT_FAILURE = 3; + } + + optional Code code = 1; + + // Textual description of the status or additional context about any + // error. (Optional - Can be set for success also.) + optional string description = 2; +} + +// Identifies an object that a client can register for. +message ObjectIdP { + + // The source of the data. + optional int32 source = 1; + + // The id of the object relative to the source. Must be <= 64 bytes. + optional bytes name = 2; +} + +// A message containing the part of the client's id that the application +// controls. This id is used for squelching invalidations on the server side. +// For example, if a client C1 modifies object x and informs the backend about +// C1's application client id as part of the invalidation. The backend can then +// avoid sending the invalidation unnecessarily to that client. +// +// If the application wishes to use this squelching feature, it must assign a +// globally unique client_name for a given client_type so that the particular +// instantation of the application can be identified. +message ApplicationClientIdP { + // The type of the client. + optional int32 client_type = 1; + + // A client name or unique id assigned by the application. Application should + // choose a unique name for different client instances if it wants to squelch + // invalidations by name (as discussed above). + optional bytes client_name = 2; +} + +// Invalidation for a given object/version. +message InvalidationP { + // The id of the object being invalidated. + optional ObjectIdP object_id = 1; + + // Whether the invalidation is for a known version of the object as assigned + // by an application backend (is_known_version == true) or an unknown system + // version synthesized by the invalidation service. (Note that if + // is_known_version is false then is_trickle_restart be true or missing + // because an unknown version implies that invalidation versions prior to the + // current backend version may have been dropped.) + optional bool is_known_version = 2; + + // Version being invalidated (see comment on is_known_version). If the + // is_known_version is false, the version corresponds to an internal "system + // version" for *that* object. An object's system version has no meaning to + // the application other than the fact that these system versions are also + // monotonically increasing and the client must ack such an invalidation with + // this system version (and an ack for a later system version acknowledges an + // invalidation for all earlier system version for *that* object. + optional int64 version = 3; + + // Whether the object's Trickle is restarting at this version. + // sets this value to true to inform Trickle API clients that it may + // have dropped invalidations prior to "version", or, if is_known_version is + // false, prior to the current backend version. + optional bool is_trickle_restart = 6 [default = false]; + + // Optional payload associated with this invalidation. + optional bytes payload = 4; + + // DEPRECATED: bridge arrival time is now maintained by + // InvalidationMetadataP in the SourcedInvalidation, InvalidationContents and + // ClientInvalidation containers. + optional int64 bridge_arrival_time_ms_deprecated = 5 [deprecated=true]; +} + +// Specifies the intention to change a registration on a specific object. To +// update registrations, a client sends a message containing repeated +// RegistrationP messages. +message RegistrationP { + enum OpType { + REGISTER = 1; + UNREGISTER = 2; + } + + // The object for which to (un)register. + optional ObjectIdP object_id = 1; + + // Whether to register or unregister. + optional OpType op_type = 2; +} + +// Summary of the registration state associated with a particular client, sent +// in the header of client<->server messages. This summary has two different +// (but related) meanings depending on where it is used: +// +// 1) In a client->server message, it describes the DESIRED client state. +// 2) In a server->client message, it describes the ACTUAL state at the server +// for that client. +message RegistrationSummary { + // Number of registrations desired (client) or held (server). + optional int32 num_registrations = 1; + + // Top-level digest over the registrations. + // + // The digest for an object id is computed as following (the digest chosen for + // this method is SHA-1): + // + // digest = new Digest(); + // digest.update(Little endian encoding of object source type) + // digest.update(object name) + // digest.getDigestSummary() + // + // For a set of objects, digest is computing by sorting lexicographically + // based on their digests and then performing the update process given above + // (i.e., calling digest.update on each object's digest and then calling + // getDigestSummary at the end). + optional bytes registration_digest = 2; +} + +// Header included on every client -> server message. +message ClientHeader { + + // Protocol version of this message. + optional ProtocolVersion protocol_version = 1; + + // Token identifying the client. Tokens are issued by the server in response + // to client requests (see InitializeMessage, below). In order to perform any + // operation other than initialization, the client must supply a token. When + // performing initialization, this field must be left unset. + optional bytes client_token = 2; + + // Optional summary of the client's desired registration state. The client is + // encouraged to provide this summary in every message once a "steady" state + // of registrations/unregistrations has been reached. For example, it may not + // want to send this summary during initialization (but after the initial set + // has been registered, it should try to send it). + optional RegistrationSummary registration_summary = 3; + + // Timestamp from the client's clock, expressed as ms since 00:00:00 UTC, 1 + // January 1970 (i.e., the UNIX epoch) - for debugging/monitoring purposes. + optional int64 client_time_ms = 4; + + // Highest server timestamp observed by the client (the server includes its + // time on every message to the client). Note: this time is NOT necessarily + // expressed as relative to the UNIX epoch - for debugging/monitoring + // purposes. + optional int64 max_known_server_time_ms = 5; + + // Message id to identify the message -for debugging/monitoring purposes. + optional string message_id = 6; + + // Client typecode (as in the InitializeMessage, below). This field may or + // may not be set. + optional int32 client_type = 7; +} + +// A message from the client to the server. +message ClientToServerMessage { + // Header. + optional ClientHeader header = 1; + + // Any or all of the follow messages may be present. + + // Optional initialization message, used to obtain a new token. Note that, if + // present, this message is always processed before the messages below, and + // those messages will be interpreted relative to the new token assigned here. + optional InitializeMessage initialize_message = 2; + + // Optional request to perform registrations. + optional RegistrationMessage registration_message = 3; + + // Optional data for registration sync. + optional RegistrationSyncMessage registration_sync_message = 4; + + // Optional invalidation acks. + optional InvalidationMessage invalidation_ack_message = 5; + + // Optional information about the client. + optional InfoMessage info_message = 6; +} + +// Used to obtain a new token when the client does not have one. +message InitializeMessage { + + // Defines how clients serialize object ids when computing digests for + // registrations. + enum DigestSerializationType { + + // The digest for an object id is computed by serializing the object id into + // bytes. + BYTE_BASED = 1; + + // The digest for an object id is computed by serializing the object id into + // an array of numbers. + NUMBER_BASED = 2; + } + + // Type of the client. This value is assigned by the backend notification + // system (out-of-band) and the client must use the correct value. + optional int32 client_type = 1; + + // Nonce. This value will be echoed as the existing token in the header of + // the server message that supplies the new token (the new token itself will + // be provided in a TokenControlMessage; see below). + optional bytes nonce = 2; + + // Id of the client as assigned by the application. + optional ApplicationClientIdP application_client_id = 3; + + // Type of registration digest used by this client. + optional DigestSerializationType digest_serialization_type = 4; +} + +// Registration operations to perform. +message RegistrationMessage { + repeated RegistrationP registration = 1; +} + +// Message from the client to the server. +message RegistrationSyncMessage { + + // Objects for which the client is registered. + repeated RegistrationSubtree subtree = 1; +} + +// Message sent from the client to the server about registered objects +// (typically) in response to a registration sync request. +// +// The name of the message implies a "tree" for future expansion where the +// intention is to not necessarily send the complete set of objects but to +// partition the object space into multiple ranges and then exchange Merkle-tree +// like data structures to determine which ranges are out-of-sync. +message RegistrationSubtree { + // Registered objects + repeated ObjectIdP registered_object = 1; +} + +// A message from the client to the server with info such as performance +// counters, client os info, etc. +message InfoMessage { + optional ClientVersion client_version = 1; + + // Config parameters used by the client. + // Deprecated and removed - the client_config parameter is what is used now. + repeated PropertyRecord config_parameter = 2; + + // Performance counters from the client. + repeated PropertyRecord performance_counter = 3; + + // If 'true', indicates that the client does not know the server's + // registration summary, so the server should respond with it even if the + // client's summary matches the server's. + optional bool server_registration_summary_requested = 4; + + // Configuration parameters for this client. + optional ClientConfigP client_config = 5; +} + +// Information about a single config/performance counter value in the +// InfoMessage. +message PropertyRecord { + + // Name of the performance counter/config parameter. + optional string name = 1; + + // Value of the performance counter/config parameter. + optional int32 value = 2; +} + +message ServerHeader { + // Protocol version of this message. + optional ProtocolVersion protocol_version = 1; + + // Current token that the server expects the client to have. Clients must + // ignore messages where this token field does not match their current token. + // During initialization, the client's "token" is the nonce that it generates + // and sends in the InitializeMessage. + optional bytes client_token = 2; + + // Summary of registration state held by the server for the client. + optional RegistrationSummary registration_summary = 3; + + // Timestamp from the server's clock. No guarantee on when this time is + // relative to. + optional int64 server_time_ms = 4; + + // Message id to identify the message (for debug purposes only). + optional string message_id = 5; +} + +message ServerToClientMessage { + optional ServerHeader header = 1; + + // Message to assign a new client token or invalidate an existing one. Note + // that, if present, this message is always processed before the messages + // below, and those messages will be interpreted relative to the new token + // assigned here. + optional TokenControlMessage token_control_message = 2; + + // Invalidations. + optional InvalidationMessage invalidation_message = 3; + + // Registration operation replies. + optional RegistrationStatusMessage registration_status_message = 4; + + // Request for client registration state. + optional RegistrationSyncRequestMessage registration_sync_request_message = 5; + + // Request to change config from the server. + optional ConfigChangeMessage config_change_message = 6; + + // Request for client information. + optional InfoRequestMessage info_request_message = 7; + + // Asynchronous error information that the server sends to the client. + optional ErrorMessage error_message = 8; +} + +// Message used to supply a new client token or invalidate an existing one. +message TokenControlMessage { + // If status is failure, new_token cannot be set. + optional bytes new_token = 1; // If missing, means destroy_token +} + +// Status of a particular registration (could be sent spontaneously by the +// server or in response to a registration request). +message RegistrationStatus { + optional RegistrationP registration = 1; + optional StatusP status = 2; +} + +// Registration status of several messages from the server to the client. +message RegistrationStatusMessage { + repeated RegistrationStatus registration_status = 1; +} + +// Request from the server to get the registration info from the client for +// sync purposes. +message RegistrationSyncRequestMessage { +} + +// A set of invalidations from the client to the server or vice-versa +message InvalidationMessage { + repeated InvalidationP invalidation = 1; +} + +// A request from the server to the client for information such as +// performance counters, client os, etc +message InfoRequestMessage { + enum InfoType { + GET_PERFORMANCE_COUNTERS = 1; + } + repeated InfoType info_type = 1; +} + +// A rate limit: a count of events and a window duration in which the events +// may occur. +message RateLimitP { + + // The size of the window over which the rate limit applies. + optional int32 window_ms = 1; + + // The number of events allowed within a given window. + optional int32 count = 2; +} + +// Configuration parameters for the protocol handler in the Ticl. +message ProtocolHandlerConfigP { + // Batching delay - certain messages (e.g., registrations, invalidation acks) + // are sent to the server after this delay. + optional int32 batching_delay_ms = 1 [default = 500]; + + // Rate limits for sending messages. Only two levels allowed currently. + repeated RateLimitP rate_limit = 2; +} + +// Configuration parameters for the Ticl. +message ClientConfigP { + + optional Version version = 1; + + // The delay after which a network message sent to the server is considered + // timed out. + optional int32 network_timeout_delay_ms = 2 [default = 60000]; + + // Retry delay for a persistent write if it fails + optional int32 write_retry_delay_ms = 3 [default = 10000]; + + // Delay for sending heartbeats to the server. + optional int32 heartbeat_interval_ms = 4 [default = 1200000]; + + // Delay after which performance counters are sent to the server. + optional int32 perf_counter_delay_ms = 5 [default = 21600000]; // 6 hours. + + // The maximum exponential backoff factor used for network and persistence + /// timeouts. + optional int32 max_exponential_backoff_factor = 6 [default = 500]; + + // Smearing percent for randomizing delays. + optional int32 smear_percent = 7 [default = 20]; + + // Whether the client is transient, that is, does not write its session + // token to durable storage. + // TODO(xiaolan): (BUG 5627144) need to expose to the clients. + // For android the default is false. But for mtrx the default is true. + optional bool is_transient = 8 [default = false]; + + // Initial delay for a heartbeat after restarting from persistent state. We + // use this so that the application has a chance to respond to the + // reissueRegistrations call. + optional int32 initial_persistent_heartbeat_delay_ms = 9 [default = 2000]; + + // Configuration for the protocol client to control batching etc. + optional ProtocolHandlerConfigP protocol_handler_config = 10; + + // Whether the channel supports delivery while the client is offline. If + // true, then the servers' use of the channel is such that the + // following holds: if any number of messages are sent to the client while + // the client is unreachable, then the channel will eventually deliver at + // least one message to the client such that, on receiving the message, the + // client will send a message to the server. E.g., the channel could deliver + // a single invalidation or a single registration sync request. C2DM is + // an example of a suitable channel. + // + // When this is true, the Ticl will record in persistent storage the last + // time it sent a message to the server. On persistent restart, it will not + // send a message to the server unless the last one was sent more than a + // heartbeat-interval ago. This is designed to support efficient Android + // clients, which will destroy and recreate the Ticl when transitioning + // between foreground and background states. + optional bool channel_supports_offline_delivery = 11 [default = false]; + + // If the client loses network connectivity, it will send a heartbeat after it + // comes online, unless it had already sent a message more recently than this + // threshold. + optional int32 offline_heartbeat_threshold_ms = 12 [default = 60000]; + + // Whether the client allows suppression. If true (the default), then + // both continuous and restarted invalidations result in an invalidate() + // upcall, which is appropriate for invalidation clients. If false, + // then restarted invalidations result in an invalidateUnknownVersion() + // upcall, which provides correct semantics for Trickles clients. + optional bool allow_suppression = 13 [default = true]; +} + +// A message asking the client to change its configuration parameters +message ConfigChangeMessage { + + // On receipt of this value, do not send any new message to the server + // for the specified delay (this message needs to be accepted without + // any token check). A zero value is ignored by the client. So the lowest + // value for this field is 1. This concept exists to allow the server + // to tell the clients that they should not come back to the server + // for some period of time. + optional int64 next_message_delay_ms = 1; +} + +// An error message that contains an enum for different types of failures with a +// textual description of the failure (as the need arises new error codes will +// be added to this message). +message ErrorMessage { + + enum Code { + AUTH_FAILURE = 1; // Authorization or authentication failure. + UNKNOWN_FAILURE = 10000; // Some failure which is not described above. + }; + + optional Code code = 1; + + // Textual description of the error + optional string description = 2; +} diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/client_test_internal.proto b/third_party/cacheinvalidation/src/google/cacheinvalidation/client_test_internal.proto new file mode 100644 index 0000000..4f8bfa9 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/client_test_internal.proto @@ -0,0 +1,31 @@ +// 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. + +// Specification of protocol buffers that are used only on the client side for +// testing. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package ipc.invalidation; + +import "client_protocol.proto"; + +// Registration manager state +message RegistrationManagerStateP { + optional RegistrationSummary client_summary = 1; + optional RegistrationSummary server_summary = 2; + repeated ObjectIdP registered_objects = 3; +} diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/callback.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/callback.h new file mode 100644 index 0000000..80670d8 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/callback.h @@ -0,0 +1,82 @@ +// Copyright 2012 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. + +// Defines callback types for the invalidation client library. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_CALLBACK_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_CALLBACK_H_ + +#include "base/callback.h" + +#define INVALIDATION_CALLBACK1_TYPE(Arg1) ::Callback1<Arg1> + +namespace invalidation { + +using ::Closure; +using ::DoNothing; +using ::NewPermanentCallback; + +template <class T> +bool IsCallbackRepeatable(const T* callback) { + return callback->IsRepeatable(); +} + +// Encapsulates a callback and its argument. Deletes the inner callback when it +// is itself deleted, regardless of whether it is ever run. +template<typename ArgumentType> +class CallbackWrapper : public Closure { + public: + // Constructs a new CallbackWrapper, which takes ownership of the inner + // callback. + CallbackWrapper( + INVALIDATION_CALLBACK1_TYPE(ArgumentType)* callback, ArgumentType arg) : + callback_(callback), arg_(arg) {} + + virtual ~CallbackWrapper() { + delete callback_; + } + + // Returns whether the inner callback is repeatable. + virtual bool IsRepeatable() const { + return callback_->IsRepeatable(); + } + + // Runs the inner callback on the argument. + virtual void Run() { + callback_->Run(arg_); + } + + private: + // The callback to run. + INVALIDATION_CALLBACK1_TYPE(ArgumentType)* callback_; + // The argument on which to run it. + ArgumentType arg_; +}; + +// An override of NewPermanentCallback that wraps a callback and its argument, +// transferring ownership of the inner callback to the new one. We define this +// here (in deps/callback.h), along with the class above, because the Google +// implementation of callbacks is much different from the one used in Chrome. A +// Chrome Closure's destructor and Run() method are not virtual, so we can't +// define custom implementations (as above in CallbackWrapper) to get the +// semantics and memory management behavior we want. +template <typename ArgType> +Closure* NewPermanentCallback( + INVALIDATION_CALLBACK1_TYPE(ArgType)* callback, ArgType arg) { + return new CallbackWrapper<ArgType>(callback, arg); +} + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_CALLBACK_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/digest-function.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/digest-function.h new file mode 100644 index 0000000..6e00f5b --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/digest-function.h @@ -0,0 +1,46 @@ +// Copyright 2012 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. + +// Interface specifying a function to compute digests. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_DIGEST_FUNCTION_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_DIGEST_FUNCTION_H_ + +#include <string> + +#include "google/cacheinvalidation/deps/stl-namespace.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::string; + +class DigestFunction { + public: + virtual ~DigestFunction() {} + + /* Clears the digest state. */ + virtual void Reset() = 0; + + /* Adds data to the digest being computed. */ + virtual void Update(const string& data) = 0; + + /* Stores the digest of the data added by Update. After this call has been + * made, reset must be called before Update and GetDigest can be called. + */ + virtual string GetDigest() = 0; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_DIGEST_FUNCTION_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/gmock.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/gmock.h new file mode 100644 index 0000000..777bfec --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/gmock.h @@ -0,0 +1,20 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_GMOCK_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_GMOCK_H_ + +#error Replace with a header that imports the Google Mock library. + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_GMOCK_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/googletest.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/googletest.h new file mode 100644 index 0000000..a0e6492 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/googletest.h @@ -0,0 +1,21 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_GOOGLETEST_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_GOOGLETEST_H_ + +#error This file should be replaced with a stub pointing to the googletest \ + header. + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_GOOGLETEST_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/logging.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/logging.h new file mode 100644 index 0000000..5639774 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/logging.h @@ -0,0 +1,22 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_LOGGING_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_LOGGING_H_ + +#error This file should be replaced with a stub pointing to the google-glog \ + header. Also there should be a LogMessage() function in the invalidation \ + namespace. + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_LOGGING_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/mutex.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/mutex.h new file mode 100644 index 0000000..f78c759 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/mutex.h @@ -0,0 +1,26 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_MUTEX_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_MUTEX_H_ + +#include "base/mutex.h" + +namespace invalidation { + +using ::Mutex; +using ::MutexLock; +} // invalidation + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_MUTEX_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/random.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/random.h new file mode 100644 index 0000000..3d3d67da --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/random.h @@ -0,0 +1,36 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_RANDOM_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_RANDOM_H_ + +#error This file should be replaced with an implementation of the following \ + interface. + +namespace invalidation { + +class Random { + public: + explicit Random(int64 seed); + + // Returns a pseudorandom value between 0 (inclusive) and 1 (exclusive). + virtual double RandDouble(); + + // Returns a pseudorandom unsigned 64-bit number. + virtual uint64 RandUint64(); +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_RANDOM_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/scoped_ptr.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/scoped_ptr.h new file mode 100644 index 0000000..91f415b --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/scoped_ptr.h @@ -0,0 +1,20 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_SCOPED_PTR_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_SCOPED_PTR_H_ + +#error Override scoped_ptr.h to point to a scoped_ptr implementation. + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_SCOPED_PTR_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/sha1-digest-function.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/sha1-digest-function.h new file mode 100644 index 0000000..ed4b7fa --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/sha1-digest-function.h @@ -0,0 +1,22 @@ +// Copyright 2012 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. + +// Interface to SHA1 digest computation. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_SHA1_DIGEST_FUNCTION_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_SHA1_DIGEST_FUNCTION_H_ + +#error Replace this file with an implementation of DigestFunction based on SHA1. + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_SHA1_DIGEST_FUNCTION_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/stl-namespace.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/stl-namespace.h new file mode 100644 index 0000000..e05bcb4 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/stl-namespace.h @@ -0,0 +1,22 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_STL_NAMESPACE_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_STL_NAMESPACE_H_ + +// Google style is to use the global namespace for stl classes so we +// leave this blank. +#define INVALIDATION_STL_NAMESPACE + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_STL_NAMESPACE_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/string_util.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/string_util.h new file mode 100644 index 0000000..8f03ca3 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/string_util.h @@ -0,0 +1,22 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_STRING_UTIL_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_STRING_UTIL_H_ + +#error This file should be replaced with a stub pointing to a file \ + containing string utility functions in the invalidation namespace. \ + At least StringAppendV() StringPrintf(), and IntToString() are needed. + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_STRING_UTIL_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/time.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/time.h new file mode 100644 index 0000000..070b1fe --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/deps/time.h @@ -0,0 +1,22 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_DEPS_TIME_H_ +#define GOOGLE_CACHEINVALIDATION_DEPS_TIME_H_ + +#error This file should be replaced with a stub pointing to something \ + like base/time.h from the Chromium source tree, with definitions for types \ + Time, TimeDelta, etc. + +#endif // GOOGLE_CACHEINVALIDATION_DEPS_TIME_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/basic-system-resources.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/basic-system-resources.cc new file mode 100644 index 0000000..0b2c24f --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/basic-system-resources.cc @@ -0,0 +1,82 @@ +// Copyright 2012 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. + +#include "google/cacheinvalidation/impl/basic-system-resources.h" + +namespace invalidation { + +BasicSystemResources::BasicSystemResources( + Logger* logger, Scheduler* internal_scheduler, + Scheduler* listener_scheduler, NetworkChannel* network, + Storage* storage, const string& platform) + : logger_(logger), + internal_scheduler_(internal_scheduler), + listener_scheduler_(listener_scheduler), + network_(network), + storage_(storage), + platform_(platform) { + logger_->SetSystemResources(this); + internal_scheduler_->SetSystemResources(this); + listener_scheduler_->SetSystemResources(this); + network_->SetSystemResources(this); + storage_->SetSystemResources(this); +} + +BasicSystemResources::~BasicSystemResources() { +} + +void BasicSystemResources::Start() { + CHECK(!run_state_.IsStarted()) << "resources already started"; + + // TODO(ghc): Investigate whether we should have Start() and Stop() methods + // on components like the scheduler. Otherwise, the resources can't start and + // stop them ... + run_state_.Start(); +} + +void BasicSystemResources::Stop() { + CHECK(run_state_.IsStarted()) << "cannot stop resources that aren't started"; + CHECK(!run_state_.IsStopped()) << "resources already stopped"; + run_state_.Stop(); +} + +bool BasicSystemResources::IsStarted() const { + return run_state_.IsStarted(); +} + +Logger* BasicSystemResources::logger() { + return logger_.get(); +} + +Scheduler* BasicSystemResources::internal_scheduler() { + return internal_scheduler_.get(); +} + +Scheduler* BasicSystemResources::listener_scheduler() { + return listener_scheduler_.get(); +} + +NetworkChannel* BasicSystemResources::network() { + return network_.get(); +} + +Storage* BasicSystemResources::storage() { + return storage_.get(); +} + +string BasicSystemResources::platform() const { + return platform_; +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/basic-system-resources.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/basic-system-resources.h new file mode 100644 index 0000000..8fc1a29 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/basic-system-resources.h @@ -0,0 +1,68 @@ +// Copyright 2012 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. + +// A simple implementation of SystemResources that just takes the resource +// components and constructs a SystemResources object. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_BASIC_SYSTEM_RESOURCES_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_BASIC_SYSTEM_RESOURCES_H_ + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/scoped_ptr.h" +#include "google/cacheinvalidation/impl/run-state.h" + +namespace invalidation { + +class BasicSystemResources : public SystemResources { + public: + // Constructs an instance from resource components. Ownership of all + // components is transferred to the BasicSystemResources object. + BasicSystemResources( + Logger* logger, Scheduler* internal_scheduler, + Scheduler* listener_scheduler, NetworkChannel* network, + Storage* storage, const string& platform); + + virtual ~BasicSystemResources(); + + // Overrides from SystemResources. + virtual void Start(); + virtual void Stop(); + virtual bool IsStarted() const; + + virtual Logger* logger(); + virtual Scheduler* internal_scheduler(); + virtual Scheduler* listener_scheduler(); + virtual NetworkChannel* network(); + virtual Storage* storage(); + virtual string platform() const; + + private: + // Components comprising the system resources. We delegate calls to these as + // appropriate. + scoped_ptr<Logger> logger_; + scoped_ptr<Scheduler> internal_scheduler_; + scoped_ptr<Scheduler> listener_scheduler_; + scoped_ptr<NetworkChannel> network_; + scoped_ptr<Storage> storage_; + + // The state of the resources. + RunState run_state_; + + // Information about the client operating system/platform. + string platform_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_BASIC_SYSTEM_RESOURCES_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/build_constants.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/build_constants.h new file mode 100644 index 0000000..156b494 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/build_constants.h @@ -0,0 +1,22 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// 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. + +// Build constant definitions + +#ifndef INVALIDATION_BUILD_CONSTANTS_H_ +#define INVALIDATION_BUILD_CONSTANTS_H_ +namespace invalidation { +const int BUILD_DATESTAMP = 20140204; +} // namespace invalidation +#endif // INVALIDATION_BUILD_CONSTANTS_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/checking-invalidation-listener.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/checking-invalidation-listener.cc new file mode 100644 index 0000000..af5a4e5 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/checking-invalidation-listener.cc @@ -0,0 +1,133 @@ +// Copyright 2012 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. + +// InvalidationListener wrapper that ensures that a delegate listener is called +// on the proper thread and calls the listener method on the listener thread. + +#include "google/cacheinvalidation/impl/checking-invalidation-listener.h" +#include "google/cacheinvalidation/impl/log-macro.h" + +namespace invalidation { + +CheckingInvalidationListener::CheckingInvalidationListener( + InvalidationListener* delegate, Statistics* statistics, + Scheduler* internal_scheduler, Scheduler* listener_scheduler, + Logger* logger) + : delegate_(delegate), + statistics_(statistics), + internal_scheduler_(internal_scheduler), + listener_scheduler_(listener_scheduler), + logger_(logger) { + CHECK(delegate != NULL); + CHECK(statistics != NULL); + CHECK(internal_scheduler_ != NULL); + CHECK(listener_scheduler != NULL); + CHECK(logger != NULL); +} + +void CheckingInvalidationListener::Invalidate( + InvalidationClient* client, const Invalidation& invalidation, + const AckHandle& ack_handle) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + statistics_->RecordListenerEvent(Statistics::ListenerEventType_INVALIDATE); + listener_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + delegate_, &InvalidationListener::Invalidate, client, invalidation, + ack_handle)); +} + +void CheckingInvalidationListener::InvalidateUnknownVersion( + InvalidationClient* client, const ObjectId& object_id, + const AckHandle& ack_handle) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + statistics_->RecordListenerEvent( + Statistics::ListenerEventType_INVALIDATE_UNKNOWN); + listener_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + delegate_, &InvalidationListener::InvalidateUnknownVersion, client, + object_id, ack_handle)); +} + +void CheckingInvalidationListener::InvalidateAll( + InvalidationClient* client, const AckHandle& ack_handle) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + statistics_->RecordListenerEvent( + Statistics::ListenerEventType_INVALIDATE_ALL); + listener_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + delegate_, &InvalidationListener::InvalidateAll, client, + ack_handle)); +} + +void CheckingInvalidationListener::InformRegistrationFailure( + InvalidationClient* client, const ObjectId& object_id, + bool is_transient, const string& error_message) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + statistics_->RecordListenerEvent( + Statistics::ListenerEventType_INFORM_REGISTRATION_FAILURE); + listener_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + delegate_, &InvalidationListener::InformRegistrationFailure, client, + object_id, is_transient, error_message)); +} + +void CheckingInvalidationListener::InformRegistrationStatus( + InvalidationClient* client, const ObjectId& object_id, + RegistrationState reg_state) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + statistics_->RecordListenerEvent( + Statistics::ListenerEventType_INFORM_REGISTRATION_STATUS); + listener_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + delegate_, &InvalidationListener::InformRegistrationStatus, client, + object_id, reg_state)); +} + +void CheckingInvalidationListener::ReissueRegistrations( + InvalidationClient* client, const string& prefix, int prefix_len) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + statistics_->RecordListenerEvent( + Statistics::ListenerEventType_REISSUE_REGISTRATIONS); + listener_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + delegate_, &InvalidationListener::ReissueRegistrations, + client, prefix, prefix_len)); +} + +void CheckingInvalidationListener::InformError( + InvalidationClient* client, const ErrorInfo& error_info) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + statistics_->RecordListenerEvent( + Statistics::ListenerEventType_INFORM_ERROR); + listener_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + delegate_, &InvalidationListener::InformError, client, error_info)); +} + +void CheckingInvalidationListener::Ready(InvalidationClient* client) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + TLOG(logger_, INFO, "Informing app that ticl is ready"); + listener_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback(delegate_, &InvalidationListener::Ready, client)); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/checking-invalidation-listener.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/checking-invalidation-listener.h new file mode 100644 index 0000000..4c82e031 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/checking-invalidation-listener.h @@ -0,0 +1,88 @@ +// Copyright 2012 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. + +// InvalidationListener wrapper that ensures that a delegate listener is called +// on the proper thread and calls the listener method on the listener thread. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_CHECKING_INVALIDATION_LISTENER_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_CHECKING_INVALIDATION_LISTENER_H_ + +#include "google/cacheinvalidation/include/invalidation-client.h" +#include "google/cacheinvalidation/include/invalidation-listener.h" +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/include/types.h" +#include "google/cacheinvalidation/impl/statistics.h" + +namespace invalidation { + +class CheckingInvalidationListener : public InvalidationListener { + public: + CheckingInvalidationListener( + InvalidationListener* delegate, Statistics* statistics, + Scheduler* internal_scheduler, Scheduler* listener_scheduler, + Logger* logger); + + virtual ~CheckingInvalidationListener() {} + + virtual void Invalidate( + InvalidationClient* client, const Invalidation& invalidation, + const AckHandle& ack_handle); + + virtual void InvalidateUnknownVersion( + InvalidationClient* client, const ObjectId& object_id, + const AckHandle& ack_handle); + + virtual void InvalidateAll( + InvalidationClient* client, const AckHandle& ack_handle); + + virtual void InformRegistrationFailure( + InvalidationClient* client, const ObjectId& object_id, + bool is_transient, const string& error_message); + + virtual void InformRegistrationStatus( + InvalidationClient* client, const ObjectId& object_id, + RegistrationState reg_state); + + virtual void ReissueRegistrations( + InvalidationClient* client, const string& prefix, int prefix_len); + + virtual void InformError( + InvalidationClient* client, const ErrorInfo& error_info); + + /* Returns the delegate InvalidationListener. */ + InvalidationListener* delegate() { + return delegate_; + } + + virtual void Ready(InvalidationClient* client); + + private: + /* The actual listener to which this listener delegates. */ + InvalidationListener* delegate_; + + /* Statistics objects to track number of sent messages, etc. */ + Statistics* statistics_; + + /* The scheduler for scheduling internal events in the library. */ + Scheduler* internal_scheduler_; + + /* The scheduler for scheduling events for the delegate. */ + Scheduler* listener_scheduler_; + + Logger* logger_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_CHECKING_INVALIDATION_LISTENER_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/client-protocol-namespace-fix.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/client-protocol-namespace-fix.h new file mode 100644 index 0000000..9366a39 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/client-protocol-namespace-fix.h @@ -0,0 +1,81 @@ +// Copyright 2012 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. + +// Brings invalidation client protocol buffers into invalidation namespace. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_CLIENT_PROTOCOL_NAMESPACE_FIX_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_CLIENT_PROTOCOL_NAMESPACE_FIX_H_ + +#include "google/cacheinvalidation/client.pb.h" +#include "google/cacheinvalidation/client_protocol.pb.h" +#include "google/cacheinvalidation/types.pb.h" +#include "google/cacheinvalidation/impl/repeated-field-namespace-fix.h" + +namespace invalidation { + +// Client +using ::ipc::invalidation::PersistentStateBlob; +using ::ipc::invalidation::PersistentTiclState; + +// ClientProtocol +using ::ipc::invalidation::AckHandleP; +using ::ipc::invalidation::ApplicationClientIdP; +using ::ipc::invalidation::ClientConfigP; +using ::ipc::invalidation::ClientHeader; +using ::ipc::invalidation::ClientVersion; +using ::ipc::invalidation::ClientToServerMessage; +using ::ipc::invalidation::ConfigChangeMessage; +using ::ipc::invalidation::ErrorMessage; +using ::ipc::invalidation::ErrorMessage_Code_AUTH_FAILURE; +using ::ipc::invalidation::ErrorMessage_Code_UNKNOWN_FAILURE; +using ::ipc::invalidation::InfoMessage; +using ::ipc::invalidation::InfoRequestMessage; +using ::ipc::invalidation::InfoRequestMessage_InfoType; +using ::ipc::invalidation::InfoRequestMessage_InfoType_GET_PERFORMANCE_COUNTERS; +using ::ipc::invalidation::InitializeMessage; +using ::ipc::invalidation::InitializeMessage_DigestSerializationType_BYTE_BASED; +using ::ipc::invalidation::InitializeMessage_DigestSerializationType_NUMBER_BASED; +using ::ipc::invalidation::InvalidationMessage; +using ::ipc::invalidation::InvalidationP; +using ::ipc::invalidation::ObjectIdP; +using ::ipc::invalidation::PropertyRecord; +using ::ipc::invalidation::ProtocolHandlerConfigP; +using ::ipc::invalidation::ProtocolVersion; +using ::ipc::invalidation::RateLimitP; +using ::ipc::invalidation::RegistrationMessage; +using ::ipc::invalidation::RegistrationP; +using ::ipc::invalidation::RegistrationP_OpType_REGISTER; +using ::ipc::invalidation::RegistrationP_OpType_UNREGISTER; +using ::ipc::invalidation::RegistrationMessage; +using ::ipc::invalidation::RegistrationStatus; +using ::ipc::invalidation::RegistrationStatusMessage; +using ::ipc::invalidation::RegistrationSubtree; +using ::ipc::invalidation::RegistrationSummary; +using ::ipc::invalidation::RegistrationSyncMessage; +using ::ipc::invalidation::RegistrationSyncRequestMessage; +using ::ipc::invalidation::ServerHeader; +using ::ipc::invalidation::ServerToClientMessage; +using ::ipc::invalidation::StatusP; +using ::ipc::invalidation::StatusP_Code_SUCCESS; +using ::ipc::invalidation::StatusP_Code_PERMANENT_FAILURE; +using ::ipc::invalidation::StatusP_Code_TRANSIENT_FAILURE; +using ::ipc::invalidation::TokenControlMessage; +using ::ipc::invalidation::Version; + +// Types +using ::ipc::invalidation::ObjectSource_Type_INTERNAL; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_CLIENT_PROTOCOL_NAMESPACE_FIX_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/constants.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/constants.cc new file mode 100644 index 0000000..e090648 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/constants.cc @@ -0,0 +1,29 @@ +// Copyright 2012 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. + +// Various constants common to clients and servers used in version 2 of the +// Ticl. + +#include "google/cacheinvalidation/impl/build_constants.h" +#include "google/cacheinvalidation/impl/constants.h" + +namespace invalidation { + +const int Constants::kClientMajorVersion = 3; +const int Constants::kClientMinorVersion = BUILD_DATESTAMP; +const int Constants::kProtocolMajorVersion = 3; +const int Constants::kProtocolMinorVersion = 2; +const int Constants::kConfigMajorVersion = 3; +const int Constants::kConfigMinorVersion = 2; +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/constants.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/constants.h new file mode 100644 index 0000000..bb604d7 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/constants.h @@ -0,0 +1,48 @@ +// Copyright 2012 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. + +// Various constants common to clients and servers used in version 2 of the +// Ticl. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_CONSTANTS_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_CONSTANTS_H_ + +namespace invalidation { + +class Constants { + public: + /* Major version of the client library. */ + static const int kClientMajorVersion; + + /* Minor version of the client library, defined to be equal to the datestamp + * of the build (e.g. 20130401). + */ + static const int kClientMinorVersion; + + /* Major version of the protocol between the client and the server. */ + static const int kProtocolMajorVersion; + + /* Minor version of the protocol between the client and the server. */ + static const int kProtocolMinorVersion; + + /* Major version of the client config. */ + static const int kConfigMajorVersion; + + /* Minor version of the client config. */ + static const int kConfigMinorVersion; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_CONSTANTS_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/digest-store.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/digest-store.h new file mode 100644 index 0000000..b81701e --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/digest-store.h @@ -0,0 +1,89 @@ +// Copyright 2012 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. + +// Interface for a store that allows objects to be added/removed along with the +// ability to get the digest for the whole or partial set of those objects. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_DIGEST_STORE_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_DIGEST_STORE_H_ + +#include <vector> + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::vector; + +template<typename ElementType> +class DigestStore { + public: + virtual ~DigestStore() {} + + /* Returns the number of elements. */ + virtual int size() = 0; + + /* Returns whether element is in the store. */ + virtual bool Contains(const ElementType& element) = 0; + + /* Returns a digest of the desired objects in 'digest'. + * + * NOTE: the digest computations MUST NOT depend on the order in which the + * elements were added. + */ + virtual string GetDigest() = 0; + + /* Stores iterators bounding the elements whose digest prefixes begin with the + * bit prefix digest_prefix. prefix_len is the length of digest_prefix in + * bits, which may be less than digest_prefix.length (and may be 0). The + * implementing class can return *more* objects than what has been specified + * by digest_prefix, e.g., it could return all the objects in the store. + */ + virtual void GetElements(const string& digest_prefix, int prefix_len, + vector<ObjectIdP>* result) = 0; + + /* Adds element to the store. No-op if element is already present. + * Returns whether the element was added. + */ + virtual bool Add(const ElementType& element) = 0; + + /* Adds elements to the store. If any element in elements is already present, + * the addition is a no-op for that element. When the function returns, + * added_elements will have been modified to contain all the elements + * of elements that were not previously present. + */ + virtual void Add(const vector<ElementType>& elements, + vector<ElementType>* added_elements) = 0; + + /* Removes element from the store. No-op if element is not present. + * Returns whether the element was removed. + */ + virtual bool Remove(const ElementType& element) = 0; + + /* Remove elements from the store. If any element in element is not present, + * the removal is a no-op for that element. + * When the function returns, removed_elements will have been modified to + * contain all the elements of elements that were previously present + */ + virtual void Remove(const vector<ElementType>& elements, + vector<ElementType>* removed_elements) = 0; + + /* Removes all elements in this and stores them in elements. */ + virtual void RemoveAll(vector<ElementType>* elements) = 0; + + /* Returns a string representation of this digest store. */ + virtual string ToString() = 0; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_DIGEST_STORE_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/exponential-backoff-delay-generator.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/exponential-backoff-delay-generator.cc new file mode 100644 index 0000000..eac02185 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/exponential-backoff-delay-generator.cc @@ -0,0 +1,40 @@ +// Copyright 2012 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. + +#include "google/cacheinvalidation/impl/exponential-backoff-delay-generator.h" + +namespace invalidation { + +TimeDelta ExponentialBackoffDelayGenerator::GetNextDelay() { + TimeDelta delay = Scheduler::NoDelay(); // After a reset, delay is zero. + if (in_retry_mode) { + // We used to multiply the current_max_delay_ by the double, but this + // implicitly truncated the double to an integer, which would always be 0. + // By converting to and from milliseconds, we avoid this problem. + delay = TimeDelta::FromMilliseconds( + random_->RandDouble() * current_max_delay_.InMilliseconds()); + + // Adjust the max for the next run. + TimeDelta max_delay = initial_max_delay_ * max_exponential_factor_; + if (current_max_delay_ <= max_delay) { // Guard against overflow. + current_max_delay_ *= 2; + if (current_max_delay_ > max_delay) { + current_max_delay_ = max_delay; + } + } + } + in_retry_mode = true; + return delay; +} +} diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/exponential-backoff-delay-generator.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/exponential-backoff-delay-generator.h new file mode 100644 index 0000000..cf36a66 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/exponential-backoff-delay-generator.h @@ -0,0 +1,79 @@ +// Copyright 2012 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. + +// Class that generates successive intervals for random exponential backoff. +// Class tracks a "high water mark" which is doubled each time getNextDelay is +// called; each call to getNextDelay returns a value uniformly randomly +// distributed between 0 (inclusive) and the high water mark (exclusive). Note +// that this class does not dictate the time units for which the delay is +// computed. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_EXPONENTIAL_BACKOFF_DELAY_GENERATOR_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_EXPONENTIAL_BACKOFF_DELAY_GENERATOR_H_ + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/scoped_ptr.h" +#include "google/cacheinvalidation/deps/logging.h" +#include "google/cacheinvalidation/deps/time.h" +#include "google/cacheinvalidation/deps/random.h" +#include "google/cacheinvalidation/impl/constants.h" + +namespace invalidation { + +class ExponentialBackoffDelayGenerator { + public: + /* Creates a generator with the given maximum and initial delays. + * Caller continues to own space for random. + */ + ExponentialBackoffDelayGenerator(Random* random, TimeDelta initial_max_delay, + int max_exponential_factor) : + initial_max_delay_(initial_max_delay), + max_exponential_factor_(max_exponential_factor), random_(random) { + CHECK_GT(max_exponential_factor_, 0) << "max factor must be positive"; + CHECK(random_ != NULL); + CHECK(initial_max_delay > Scheduler::NoDelay()) << + "Initial delay must be positive"; + Reset(); + } + + /* Resets the exponential backoff generator to start delays at the initial + * delay. + */ + void Reset() { + current_max_delay_ = initial_max_delay_; + in_retry_mode = false; + } + + /* Gets the next delay interval to use. */ + TimeDelta GetNextDelay(); + + private: + /* Initial delay time to use. */ + TimeDelta initial_max_delay_; + + /* Maximum allowed delay time. */ + int max_exponential_factor_; + + /* Next delay time to use. */ + TimeDelta current_max_delay_; + + /* If the first call to getNextDelay has been made after reset. */ + bool in_retry_mode; + + Random* random_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_EXPONENTIAL_BACKOFF_DELAY_GENERATOR_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-core.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-core.cc new file mode 100644 index 0000000..f8e637d --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-core.cc @@ -0,0 +1,1009 @@ +// Copyright 2012 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. + +// Implementation of the Invalidation Client Library (Ticl). + +#include "google/cacheinvalidation/impl/invalidation-client-core.h" + +#include <sstream> + +#include "google/cacheinvalidation/client_test_internal.pb.h" +#include "google/cacheinvalidation/deps/callback.h" +#include "google/cacheinvalidation/deps/random.h" +#include "google/cacheinvalidation/deps/sha1-digest-function.h" +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/impl/exponential-backoff-delay-generator.h" +#include "google/cacheinvalidation/impl/invalidation-client-util.h" +#include "google/cacheinvalidation/impl/log-macro.h" +#include "google/cacheinvalidation/impl/persistence-utils.h" +#include "google/cacheinvalidation/impl/proto-converter.h" +#include "google/cacheinvalidation/impl/proto-helpers.h" +#include "google/cacheinvalidation/impl/recurring-task.h" +#include "google/cacheinvalidation/impl/smearer.h" + +namespace invalidation { + +using ::ipc::invalidation::RegistrationManagerStateP; + +const char* InvalidationClientCore::kClientTokenKey = "ClientToken"; + +// AcquireTokenTask + +AcquireTokenTask::AcquireTokenTask(InvalidationClientCore* client) + : RecurringTask( + "AcquireToken", + client->internal_scheduler_, + client->logger_, + &client->smearer_, + client->CreateExpBackOffGenerator(TimeDelta::FromMilliseconds( + client->config_.network_timeout_delay_ms())), + Scheduler::NoDelay(), + TimeDelta::FromMilliseconds( + client->config_.network_timeout_delay_ms())), + client_(client) { + } + +bool AcquireTokenTask::RunTask() { + // If token is still not assigned (as expected), sends a request. + // Otherwise, ignore. + if (client_->client_token_.empty()) { + // Allocate a nonce and send a message requesting a new token. + client_->set_nonce( + InvalidationClientCore::GenerateNonce(client_->random_.get())); + + client_->protocol_handler_.SendInitializeMessage( + client_->application_client_id_, client_->nonce_, + client_->batching_task_.get(), + "AcquireToken"); + // Reschedule to check state, retry if necessary after timeout. + return true; + } else { + return false; // Don't reschedule. + } +} + +// RegSyncHeartbeatTask + +RegSyncHeartbeatTask::RegSyncHeartbeatTask(InvalidationClientCore* client) + : RecurringTask( + "RegSyncHeartbeat", + client->internal_scheduler_, + client->logger_, + &client->smearer_, + client->CreateExpBackOffGenerator(TimeDelta::FromMilliseconds( + client->config_.network_timeout_delay_ms())), + TimeDelta::FromMilliseconds( + client->config_.network_timeout_delay_ms()), + TimeDelta::FromMilliseconds( + client->config_.network_timeout_delay_ms())), + client_(client) { +} + +bool RegSyncHeartbeatTask::RunTask() { + if (!client_->registration_manager_.IsStateInSyncWithServer()) { + // Simply send an info message to ensure syncing happens. + TLOG(client_->logger_, INFO, "Registration state not in sync with " + "server: %s", client_->registration_manager_.ToString().c_str()); + client_->SendInfoMessageToServer(false, true /* request server summary */); + return true; + } else { + TLOG(client_->logger_, INFO, "Not sending message since state is in sync"); + return false; + } +} + +// PersistentWriteTask + +PersistentWriteTask::PersistentWriteTask(InvalidationClientCore* client) + : RecurringTask( + "PersistentWrite", + client->internal_scheduler_, + client->logger_, + &client->smearer_, + client->CreateExpBackOffGenerator(TimeDelta::FromMilliseconds( + client->config_.write_retry_delay_ms())), + Scheduler::NoDelay(), + TimeDelta::FromMilliseconds( + client->config_.write_retry_delay_ms())), + client_(client) { +} + +bool PersistentWriteTask::RunTask() { + if (client_->client_token_.empty() || + (client_->client_token_ == last_written_token_)) { + // No work to be done + return false; // Do not reschedule + } + + // Persistent write needs to happen. + PersistentTiclState state; + state.set_client_token(client_->client_token_); + string serialized_state; + PersistenceUtils::SerializeState(state, client_->digest_fn_.get(), + &serialized_state); + client_->storage_->WriteKey(InvalidationClientCore::kClientTokenKey, + serialized_state, + NewPermanentCallback(this, &PersistentWriteTask::WriteCallback, + client_->client_token_)); + return true; // Reschedule after timeout to make sure that write does happen. +} + +void PersistentWriteTask::WriteCallback(const string& token, Status status) { + TLOG(client_->logger_, INFO, "Write state completed: %d, %s", + status.IsSuccess(), status.message().c_str()); + if (status.IsSuccess()) { + // Set lastWrittenToken to be the token that was written (NOT client_token_: + // which could have changed while the write was happening). + last_written_token_ = token; + } else { + client_->statistics_->RecordError( + Statistics::ClientErrorType_PERSISTENT_WRITE_FAILURE); + } +} + +// HeartbeatTask + +HeartbeatTask::HeartbeatTask(InvalidationClientCore* client) + : RecurringTask( + "Heartbeat", + client->internal_scheduler_, + client->logger_, + &client->smearer_, + NULL, + TimeDelta::FromMilliseconds( + client->config_.heartbeat_interval_ms()), + Scheduler::NoDelay()), + client_(client) { + next_performance_send_time_ = client_->internal_scheduler_->GetCurrentTime() + + smearer()->GetSmearedDelay(TimeDelta::FromMilliseconds( + client_->config_.perf_counter_delay_ms())); +} + +bool HeartbeatTask::RunTask() { + // Send info message. If needed, send performance counters and reset the next + // performance counter send time. + TLOG(client_->logger_, INFO, "Sending heartbeat to server: %s", + client_->ToString().c_str()); + Scheduler *scheduler = client_->internal_scheduler_; + bool must_send_perf_counters = + next_performance_send_time_ > scheduler->GetCurrentTime(); + if (must_send_perf_counters) { + next_performance_send_time_ = scheduler->GetCurrentTime() + + client_->smearer_.GetSmearedDelay(TimeDelta::FromMilliseconds( + client_->config_.perf_counter_delay_ms())); + } + + TLOG(client_->logger_, INFO, "Sending heartbeat to server: %s", + client_->ToString().c_str()); + client_->SendInfoMessageToServer(must_send_perf_counters, + !client_->registration_manager_.IsStateInSyncWithServer()); + return true; // Reschedule. +} + +BatchingTask::BatchingTask( + ProtocolHandler *handler, Smearer* smearer, TimeDelta batching_delay) + : RecurringTask( + "Batching", handler->internal_scheduler_, handler->logger_, smearer, + NULL, batching_delay, Scheduler::NoDelay()), + protocol_handler_(handler) { +} + +bool BatchingTask::RunTask() { + // Send message to server - the batching information is picked up in + // SendMessageToServer. + protocol_handler_->SendMessageToServer(); + return false; // Don't reschedule. +} + +InvalidationClientCore::InvalidationClientCore( + SystemResources* resources, Random* random, int client_type, + const string& client_name, const ClientConfigP& config, + const string& application_name) + : resources_(resources), + internal_scheduler_(resources->internal_scheduler()), + logger_(resources->logger()), + storage_(new SafeStorage(resources->storage())), + statistics_(new Statistics()), + config_(config), + digest_fn_(new Sha1DigestFunction()), + registration_manager_(logger_, statistics_.get(), digest_fn_.get()), + msg_validator_(new TiclMessageValidator(logger_)), + smearer_(random, config.smear_percent()), + protocol_handler_(config.protocol_handler_config(), resources, &smearer_, + statistics_.get(), client_type, application_name, this, + msg_validator_.get()), + is_online_(true), + random_(random) { + storage_.get()->SetSystemResources(resources_); + application_client_id_.set_client_name(client_name); + application_client_id_.set_client_type(client_type); + CreateSchedulingTasks(); + RegisterWithNetwork(resources); + TLOG(logger_, INFO, "Created client: %s", ToString().c_str()); +} + +void InvalidationClientCore::RegisterWithNetwork(SystemResources* resources) { + // Install ourselves as a receiver for server messages. + resources->network()->SetMessageReceiver( + NewPermanentCallback(this, &InvalidationClientCore::MessageReceiver)); + + resources->network()->AddNetworkStatusReceiver( + NewPermanentCallback(this, + &InvalidationClientCore::NetworkStatusReceiver)); +} + +void InvalidationClientCore::CreateSchedulingTasks() { + acquire_token_task_.reset(new AcquireTokenTask(this)); + reg_sync_heartbeat_task_.reset(new RegSyncHeartbeatTask(this)); + persistent_write_task_.reset(new PersistentWriteTask(this)); + heartbeat_task_.reset(new HeartbeatTask(this)); + batching_task_.reset(new BatchingTask(&protocol_handler_, + &smearer_, + TimeDelta::FromMilliseconds( + config_.protocol_handler_config().batching_delay_ms()))); +} + +void InvalidationClientCore::InitConfig(ClientConfigP* config) { + ProtoHelpers::InitConfigVersion(config->mutable_version()); + ProtocolHandler::InitConfig(config->mutable_protocol_handler_config()); +} + +void InvalidationClientCore::InitConfigForTest(ClientConfigP* config) { + ProtoHelpers::InitConfigVersion(config->mutable_version()); + config->set_network_timeout_delay_ms(2000); + config->set_heartbeat_interval_ms(5000); + config->set_write_retry_delay_ms(500); + ProtocolHandler::InitConfigForTest(config->mutable_protocol_handler_config()); +} + +void InvalidationClientCore::Start() { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + if (ticl_state_.IsStarted()) { + TLOG(logger_, SEVERE, + "Ignoring start call since already started: client = %s", + this->ToString().c_str()); + return; + } + + // Initialize the nonce so that we can maintain the invariant that exactly + // one of "nonce_" and "client_token_" is non-empty. + set_nonce(InvalidationClientCore::GenerateNonce(random_.get())); + + TLOG(logger_, INFO, "Starting with C++ config: %s", + ProtoHelpers::ToString(config_).c_str()); + + // Read the state blob and then schedule startInternal once the value is + // there. + ScheduleStartAfterReadingStateBlob(); +} + +void InvalidationClientCore::StartInternal(const string& serialized_state) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + CHECK(resources_->IsStarted()) << "Resources must be started before starting " + "the Ticl"; + + // Initialize the session manager using the persisted client token. + PersistentTiclState persistent_state; + bool deserialized = false; + if (!serialized_state.empty()) { + deserialized = PersistenceUtils::DeserializeState( + logger_, serialized_state, digest_fn_.get(), &persistent_state); + } + + if (!serialized_state.empty() && !deserialized) { + // In this case, we'll proceed as if we had no persistent state -- i.e., + // obtain a new client id from the server. + statistics_->RecordError( + Statistics::ClientErrorType_PERSISTENT_DESERIALIZATION_FAILURE); + TLOG(logger_, SEVERE, "Failed deserializing persistent state: %s", + ProtoHelpers::ToString(serialized_state).c_str()); + } + if (deserialized) { + // If we have persistent state, use the previously-stored token and send a + // heartbeat to let the server know that we've restarted, since we may have + // been marked offline. + // + // In the common case, the server will already have all of our + // registrations, but we won't know for sure until we've gotten its summary. + // We'll ask the application for all of its registrations, but to avoid + // making the registrar redo the work of performing registrations that + // probably already exist, we'll suppress sending them to the registrar. + TLOG(logger_, INFO, "Restarting from persistent state: %s", + ProtoHelpers::ToString( + persistent_state.client_token()).c_str()); + set_nonce(""); + set_client_token(persistent_state.client_token()); + should_send_registrations_ = false; + + // Schedule an info message for the near future. We delay a little bit to + // allow the application to reissue its registrations locally and avoid + // triggering registration sync with the data center due to a hash mismatch. + internal_scheduler_->Schedule(TimeDelta::FromMilliseconds( + config_.initial_persistent_heartbeat_delay_ms()), + NewPermanentCallback(this, + &InvalidationClientCore::SendInfoMessageToServer, false, true)); + + // We need to ensure that heartbeats are sent, regardless of whether we + // start fresh or from persistent state. The line below ensures that they + // are scheduled in the persistent startup case. For the other case, the + // task is scheduled when we acquire a token. + heartbeat_task_.get()->EnsureScheduled("Startup-after-persistence"); + } else { + // If we had no persistent state or couldn't deserialize the state that we + // had, start fresh. Request a new client identifier. + // + // The server can't possibly have our registrations, so whatever we get + // from the application we should send to the registrar. + TLOG(logger_, INFO, "Starting with no previous state"); + should_send_registrations_ = true; + ScheduleAcquireToken("Startup"); + } + // InvalidationListener.Ready() is called when the ticl has acquired a + // new token. +} + +void InvalidationClientCore::Stop() { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + TLOG(logger_, WARNING, "Ticl being stopped: %s", ToString().c_str()); + if (ticl_state_.IsStarted()) { + ticl_state_.Stop(); + } +} + +void InvalidationClientCore::Register(const ObjectId& object_id) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + vector<ObjectId> object_ids; + object_ids.push_back(object_id); + PerformRegisterOperations(object_ids, RegistrationP_OpType_REGISTER); +} + +void InvalidationClientCore::Unregister(const ObjectId& object_id) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + vector<ObjectId> object_ids; + object_ids.push_back(object_id); + PerformRegisterOperations(object_ids, RegistrationP_OpType_UNREGISTER); +} + +void InvalidationClientCore::PerformRegisterOperations( + const vector<ObjectId>& object_ids, RegistrationP::OpType reg_op_type) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + CHECK(!object_ids.empty()) << "Must specify some object id"; + + if (ticl_state_.IsStopped()) { + // The Ticl has been stopped. This might be some old registration op + // coming in. Just ignore instead of crashing. + TLOG(logger_, SEVERE, "Ticl stopped: register (%d) of %d objects ignored.", + reg_op_type, object_ids.size()); + return; + } + if (!ticl_state_.IsStarted()) { + // We must be in the NOT_STARTED state, since we can't be in STOPPED or + // STARTED (since the previous if-check didn't succeeded, and isStarted uses + // a != STARTED test). + TLOG(logger_, SEVERE, + "Ticl is not yet started; failing registration call; client = %s, " + "num-objects = %d, op = %d", + this->ToString().c_str(), object_ids.size(), reg_op_type); + for (size_t i = 0; i < object_ids.size(); ++i) { + const ObjectId& object_id = object_ids[i]; + GetListener()->InformRegistrationFailure(this, object_id, true, + "Client not yet ready"); + } + return; + } + + vector<ObjectIdP> object_id_protos; + for (size_t i = 0; i < object_ids.size(); ++i) { + const ObjectId& object_id = object_ids[i]; + ObjectIdP object_id_proto; + ProtoConverter::ConvertToObjectIdProto(object_id, &object_id_proto); + Statistics::IncomingOperationType op_type = + (reg_op_type == RegistrationP_OpType_REGISTER) ? + Statistics::IncomingOperationType_REGISTRATION : + Statistics::IncomingOperationType_UNREGISTRATION; + statistics_->RecordIncomingOperation(op_type); + TLOG(logger_, INFO, "Register %s, %d", + ProtoHelpers::ToString(object_id_proto).c_str(), reg_op_type); + object_id_protos.push_back(object_id_proto); + } + + + // Update the registration manager state, then have the protocol client send a + // message. + vector<ObjectIdP> object_id_protos_to_send; + registration_manager_.PerformOperations(object_id_protos, reg_op_type, + &object_id_protos_to_send); + + // Check whether we should suppress sending registrations because we don't + // yet know the server's summary. + if (should_send_registrations_ && (!object_id_protos_to_send.empty())) { + protocol_handler_.SendRegistrations( + object_id_protos_to_send, reg_op_type, batching_task_.get()); + } + reg_sync_heartbeat_task_.get()->EnsureScheduled("PerformRegister"); +} + +void InvalidationClientCore::Acknowledge(const AckHandle& acknowledge_handle) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + if (acknowledge_handle.IsNoOp()) { + // Nothing to do. We do not increment statistics here since this is a no op + // handle and statistics can only be acccessed on the scheduler thread. + return; + } + // Validate the ack handle. + + // 1. Parse the ack handle first. + AckHandleP ack_handle; + ack_handle.ParseFromString(acknowledge_handle.handle_data()); + if (!ack_handle.IsInitialized()) { + TLOG(logger_, WARNING, "Bad ack handle : %s", + ProtoHelpers::ToString(acknowledge_handle.handle_data()).c_str()); + statistics_->RecordError( + Statistics::ClientErrorType_ACKNOWLEDGE_HANDLE_FAILURE); + return; + } + + // 2. Validate ack handle - it should have a valid invalidation. + if (!ack_handle.has_invalidation() + || !msg_validator_->IsValid(ack_handle.invalidation())) { + TLOG(logger_, WARNING, "Incorrect ack handle: %s", + ProtoHelpers::ToString(ack_handle).c_str()); + statistics_->RecordError( + Statistics::ClientErrorType_ACKNOWLEDGE_HANDLE_FAILURE); + return; + } + + // Currently, only invalidations have non-trivial ack handle. + InvalidationP* invalidation = ack_handle.mutable_invalidation(); + invalidation->clear_payload(); // Don't send the payload back. + statistics_->RecordIncomingOperation( + Statistics::IncomingOperationType_ACKNOWLEDGE); + protocol_handler_.SendInvalidationAck(*invalidation, batching_task_.get()); +} + +string InvalidationClientCore::ToString() { + return StringPrintf("Client: %s, %s, %s", + ProtoHelpers::ToString(application_client_id_).c_str(), + ProtoHelpers::ToString(client_token_).c_str(), + this->ticl_state_.ToString().c_str()); +} + +string InvalidationClientCore::GetClientToken() { + CHECK(client_token_.empty() || nonce_.empty()); + TLOG(logger_, FINE, "Return client token = %s", + ProtoHelpers::ToString(client_token_).c_str()); + return client_token_; +} + +void InvalidationClientCore::HandleIncomingMessage(const string& message) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + statistics_->RecordReceivedMessage( + Statistics::ReceivedMessageType_TOTAL); + ParsedMessage parsed_message; + if (!protocol_handler_.HandleIncomingMessage(message, &parsed_message)) { + // Invalid message. + return; + } + + // Ensure we have either a matching token or a matching nonce. + if (!ValidateToken(parsed_message.header.token())) { + return; + } + + // Handle a token control message, if present. + if (parsed_message.token_control_message != NULL) { + statistics_->RecordReceivedMessage( + Statistics::ReceivedMessageType_TOKEN_CONTROL); + HandleTokenChanged(parsed_message.header.token(), + parsed_message.token_control_message->new_token()); + } + + // We might have lost our token or failed to acquire one. Ensure that we do + // not proceed in either case. + // Note that checking for the presence of a TokenControlMessage is *not* + // sufficient: it might be a token-assign with the wrong nonce or a + // token-destroy message, for example. + if (client_token_.empty()) { + return; + } + + // Handle the messages received from the server by calling the appropriate + // listener method. + + // In the beginning inform the listener about the header (the caller is + // already prepared to handle the fact that the same header is given to + // it multiple times). + HandleIncomingHeader(parsed_message.header); + + if (parsed_message.invalidation_message != NULL) { + statistics_->RecordReceivedMessage( + Statistics::ReceivedMessageType_INVALIDATION); + HandleInvalidations(parsed_message.invalidation_message->invalidation()); + } + if (parsed_message.registration_status_message != NULL) { + statistics_->RecordReceivedMessage( + Statistics::ReceivedMessageType_REGISTRATION_STATUS); + HandleRegistrationStatus( + parsed_message.registration_status_message->registration_status()); + } + if (parsed_message.registration_sync_request_message != NULL) { + statistics_->RecordReceivedMessage( + Statistics::ReceivedMessageType_REGISTRATION_SYNC_REQUEST); + HandleRegistrationSyncRequest(); + } + if (parsed_message.info_request_message != NULL) { + statistics_->RecordReceivedMessage( + Statistics::ReceivedMessageType_INFO_REQUEST); + HandleInfoMessage( + // Shouldn't have to do this, but the proto compiler generates bad code + // for repeated enum fields. + *reinterpret_cast<const RepeatedField<InfoRequestMessage_InfoType>* >( + &parsed_message.info_request_message->info_type())); + } + if (parsed_message.error_message != NULL) { + statistics_->RecordReceivedMessage( + Statistics::ReceivedMessageType_ERROR); + HandleErrorMessage( + parsed_message.error_message->code(), + parsed_message.error_message->description()); + } +} + +void InvalidationClientCore::HandleTokenChanged( + const string& header_token, const string& new_token) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + // The server is either supplying a new token in response to an + // InitializeMessage, spontaneously destroying a token we hold, or + // spontaneously upgrading a token we hold. + + if (!new_token.empty()) { + // Note: header_token cannot be empty, so an empty nonce or client_token will + // always be non-equal. + bool header_token_matches_nonce = header_token == nonce_; + bool header_token_matches_existing_token = header_token == client_token_; + bool should_accept_token = + header_token_matches_nonce || header_token_matches_existing_token; + if (!should_accept_token) { + TLOG(logger_, INFO, "Ignoring new token; %s does not match nonce = %s " + "or existing token = %s", + ProtoHelpers::ToString(new_token).c_str(), + ProtoHelpers::ToString(nonce_).c_str(), + ProtoHelpers::ToString(client_token_).c_str()); + return; + } + TLOG(logger_, INFO, "New token being assigned at client: %s, Old = %s", + ProtoHelpers::ToString(new_token).c_str(), + ProtoHelpers::ToString(client_token_).c_str()); + heartbeat_task_.get()->EnsureScheduled("Heartbeat-after-new-token"); + set_nonce(""); + set_client_token(new_token); + persistent_write_task_.get()->EnsureScheduled("Write-after-new-token"); + } else { + // Destroy the existing token. + TLOG(logger_, INFO, "Destroying existing token: %s", + ProtoHelpers::ToString(client_token_).c_str()); + ScheduleAcquireToken("Destroy"); + } +} + +void InvalidationClientCore::ScheduleAcquireToken(const string& debug_string) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + set_client_token(""); + acquire_token_task_.get()->EnsureScheduled(debug_string); +} + +void InvalidationClientCore::HandleInvalidations( + const RepeatedPtrField<InvalidationP>& invalidations) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + for (int i = 0; i < invalidations.size(); ++i) { + const InvalidationP& invalidation = invalidations.Get(i); + AckHandleP ack_handle_proto; + ack_handle_proto.mutable_invalidation()->CopyFrom(invalidation); + string serialized; + ack_handle_proto.SerializeToString(&serialized); + AckHandle ack_handle(serialized); + if (ProtoConverter::IsAllObjectIdP(invalidation.object_id())) { + TLOG(logger_, INFO, "Issuing invalidate all"); + GetListener()->InvalidateAll(this, ack_handle); + } else { + // Regular object. Could be unknown version or not. + Invalidation inv; + ProtoConverter::ConvertFromInvalidationProto(invalidation, &inv); + bool isSuppressed = invalidation.is_trickle_restart(); + TLOG(logger_, INFO, "Issuing invalidate: %s", + ProtoHelpers::ToString(invalidation).c_str()); + + // Issue invalidate if the invalidation had a known version AND either + // no suppression has occurred or the client allows suppression. + if (invalidation.is_known_version() && + (!isSuppressed || config_.allow_suppression())) { + GetListener()->Invalidate(this, inv, ack_handle); + } else { + // Unknown version + GetListener()->InvalidateUnknownVersion(this, + inv.object_id(), ack_handle); + } + } + } +} + +void InvalidationClientCore::HandleRegistrationStatus( + const RepeatedPtrField<RegistrationStatus>& reg_status_list) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + vector<bool> local_processing_statuses; + registration_manager_.HandleRegistrationStatus( + reg_status_list, &local_processing_statuses); + CHECK(local_processing_statuses.size() == + static_cast<size_t>(reg_status_list.size())) << + "Not all registration statuses were processed"; + + // Inform app about the success or failure of each registration based + // on what the registration manager has indicated. + for (int i = 0; i < reg_status_list.size(); ++i) { + const RegistrationStatus& reg_status = reg_status_list.Get(i); + bool was_success = local_processing_statuses[i]; + TLOG(logger_, FINE, "Process reg status: %s", + ProtoHelpers::ToString(reg_status).c_str()); + + ObjectId object_id; + ProtoConverter::ConvertFromObjectIdProto( + reg_status.registration().object_id(), &object_id); + if (was_success) { + // Server operation was both successful and agreed with what the client + // wanted. + RegistrationP::OpType reg_op_type = reg_status.registration().op_type(); + InvalidationListener::RegistrationState reg_state = + ConvertOpTypeToRegState(reg_op_type); + GetListener()->InformRegistrationStatus(this, object_id, reg_state); + } else { + // Server operation either failed or disagreed with client's intent (e.g., + // successful unregister, but the client wanted a registration). + string description = + (reg_status.status().code() == StatusP_Code_SUCCESS) ? + "Registration discrepancy detected" : + reg_status.status().description(); + bool is_permanent = + (reg_status.status().code() == StatusP_Code_PERMANENT_FAILURE); + GetListener()->InformRegistrationFailure( + this, object_id, !is_permanent, description); + } + } +} + +void InvalidationClientCore::HandleRegistrationSyncRequest() { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + // Send all the registrations in the reg sync message. + // Generate a single subtree for all the registrations. + RegistrationSubtree subtree; + registration_manager_.GetRegistrations("", 0, &subtree); + protocol_handler_.SendRegistrationSyncSubtree(subtree, batching_task_.get()); +} + +void InvalidationClientCore::HandleInfoMessage( + const RepeatedField<InfoRequestMessage_InfoType>& info_types) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + bool must_send_performance_counters = false; + for (int i = 0; i < info_types.size(); ++i) { + must_send_performance_counters = + (info_types.Get(i) == + InfoRequestMessage_InfoType_GET_PERFORMANCE_COUNTERS); + if (must_send_performance_counters) { + break; + } + } + SendInfoMessageToServer(must_send_performance_counters, + !registration_manager_.IsStateInSyncWithServer()); +} + +void InvalidationClientCore::HandleErrorMessage( + ErrorMessage::Code code, + const string& description) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + // If it is an auth failure, we shut down the ticl. + TLOG(logger_, SEVERE, "Received error message: %s, %s", + ProtoHelpers::ToString(code).c_str(), + description.c_str()); + + // Translate the code to error reason. + int reason; + switch (code) { + case ErrorMessage_Code_AUTH_FAILURE: + reason = ErrorReason::AUTH_FAILURE; + break; + case ErrorMessage_Code_UNKNOWN_FAILURE: + reason = ErrorReason::UNKNOWN_FAILURE; + break; + default: + reason = ErrorReason::UNKNOWN_FAILURE; + break; + } + // Issue an informError to the application. + ErrorInfo error_info(reason, false, description, ErrorContext()); + GetListener()->InformError(this, error_info); + + // If this is an auth failure, remove registrations and stop the Ticl. + // Otherwise do nothing. + if (code != ErrorMessage_Code_AUTH_FAILURE) { + return; + } + + // If there are any registrations, remove them and issue registration + // failure. + vector<ObjectIdP> desired_registrations; + registration_manager_.RemoveRegisteredObjects(&desired_registrations); + TLOG(logger_, WARNING, "Issuing failure for %d objects", + desired_registrations.size()); + for (size_t i = 0; i < desired_registrations.size(); ++i) { + ObjectId object_id; + ProtoConverter::ConvertFromObjectIdProto( + desired_registrations[i], &object_id); + GetListener()->InformRegistrationFailure( + this, object_id, false, "Auth error"); + } +} + +void InvalidationClientCore::HandleMessageSent() { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + last_message_send_time_ = internal_scheduler_->GetCurrentTime(); +} + +void InvalidationClientCore::HandleNetworkStatusChange(bool is_online) { + // If we're back online and haven't sent a message to the server in a while, + // send a heartbeat to make sure the server knows we're online. + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + bool was_online = is_online_; + is_online_ = is_online; + if (is_online && !was_online && + (internal_scheduler_->GetCurrentTime() > + last_message_send_time_ + TimeDelta::FromMilliseconds( + config_.offline_heartbeat_threshold_ms()))) { + TLOG(logger_, INFO, + "Sending heartbeat after reconnection; previous send was %s ms ago", + SimpleItoa( + (internal_scheduler_->GetCurrentTime() - last_message_send_time_) + .InMilliseconds()).c_str()); + SendInfoMessageToServer( + false, !registration_manager_.IsStateInSyncWithServer()); + } +} + +void InvalidationClientCore::GetRegistrationManagerStateAsSerializedProto( + string* result) { + RegistrationManagerStateP reg_state; + registration_manager_.GetClientSummary(reg_state.mutable_client_summary()); + registration_manager_.GetServerSummary(reg_state.mutable_server_summary()); + vector<ObjectIdP> registered_objects; + registration_manager_.GetRegisteredObjectsForTest(®istered_objects); + for (size_t i = 0; i < registered_objects.size(); ++i) { + reg_state.add_registered_objects()->CopyFrom(registered_objects[i]); + } + reg_state.SerializeToString(result); +} + +void InvalidationClientCore::GetStatisticsAsSerializedProto( + string* result) { + vector<pair<string, int> > properties; + statistics_->GetNonZeroStatistics(&properties); + InfoMessage info_message; + for (size_t i = 0; i < properties.size(); ++i) { + PropertyRecord* record = info_message.add_performance_counter(); + record->set_name(properties[i].first); + record->set_value(properties[i].second); + } + info_message.SerializeToString(result); +} + +void InvalidationClientCore::HandleIncomingHeader( + const ServerMessageHeader& header) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + CHECK(nonce_.empty()) << + "Cannot process server header " << header.ToString() << + " with non-empty nonce " << nonce_; + + if (header.registration_summary() != NULL) { + // We've received a summary from the server, so if we were suppressing + // registrations, we should now allow them to go to the registrar. + should_send_registrations_ = true; + + + // Pass the registration summary to the registration manager. If we are now + // in agreement with the server and we had any pending operations, we can + // tell the listener that those operations have succeeded. + vector<RegistrationP> upcalls; + registration_manager_.InformServerRegistrationSummary( + *header.registration_summary(), &upcalls); + TLOG(logger_, FINE, + "Receivced new server registration summary (%s); will make %d upcalls", + ProtoHelpers::ToString(*header.registration_summary()).c_str(), + upcalls.size()); + vector<RegistrationP>::iterator iter; + for (iter = upcalls.begin(); iter != upcalls.end(); iter++) { + const RegistrationP& registration = *iter; + ObjectId object_id; + ProtoConverter::ConvertFromObjectIdProto(registration.object_id(), + &object_id); + InvalidationListener::RegistrationState reg_state = + ConvertOpTypeToRegState(registration.op_type()); + GetListener()->InformRegistrationStatus(this, object_id, reg_state); + } + } +} + +bool InvalidationClientCore::ValidateToken(const string& server_token) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + if (!client_token_.empty()) { + // Client token case. + if (client_token_ != server_token) { + TLOG(logger_, INFO, "Incoming message has bad token: %s, %s", + ProtoHelpers::ToString(client_token_).c_str(), + ProtoHelpers::ToString(server_token).c_str()); + statistics_->RecordError(Statistics::ClientErrorType_TOKEN_MISMATCH); + return false; + } + return true; + } else if (!nonce_.empty()) { + // Nonce case. + CHECK(!nonce_.empty()) << "Client token and nonce are both empty: " + << client_token_ << ", " << nonce_; + if (nonce_ != server_token) { + statistics_->RecordError(Statistics::ClientErrorType_NONCE_MISMATCH); + TLOG(logger_, INFO, + "Rejecting server message with mismatched nonce: Client = %s, " + "Server = %s", ProtoHelpers::ToString(nonce_).c_str(), + ProtoHelpers::ToString(server_token).c_str()); + return false; + } else { + TLOG(logger_, INFO, + "Accepting server message with matching nonce: %s", + ProtoHelpers::ToString(nonce_).c_str()); + return true; + } + } + // Neither token nor nonce; ignore message. + return false; +} + +void InvalidationClientCore::SendInfoMessageToServer( + bool must_send_performance_counters, bool request_server_summary) { + TLOG(logger_, INFO, + "Sending info message to server; request server summary = %s", + request_server_summary ? "true" : "false"); + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + // Make sure that you have the latest registration summary. + vector<pair<string, int> > performance_counters; + ClientConfigP* config_to_send = NULL; + if (must_send_performance_counters) { + statistics_->GetNonZeroStatistics(&performance_counters); + config_to_send = &config_; + } + protocol_handler_.SendInfoMessage(performance_counters, config_to_send, + request_server_summary, batching_task_.get()); +} + +string InvalidationClientCore::GenerateNonce(Random* random) { + // Return a nonce computed by converting a random 64-bit number to a string. + return SimpleItoa(static_cast<int64>(random->RandUint64())); +} + +void InvalidationClientCore::set_nonce(const string& new_nonce) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + CHECK(new_nonce.empty() || client_token_.empty()) << + "Tried to set nonce with existing token " << client_token_; + nonce_ = new_nonce; +} + +void InvalidationClientCore::set_client_token(const string& new_client_token) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + CHECK(new_client_token.empty() || nonce_.empty()) << + "Tried to set token with existing nonce " << nonce_; + + // If the ticl has not been started and we are getting a new token (either + // from persistence or from the server, start the ticl and inform the + // application. + bool finish_starting_ticl = !ticl_state_.IsStarted() && + client_token_.empty() && !new_client_token.empty(); + client_token_ = new_client_token; + + if (finish_starting_ticl) { + FinishStartingTiclAndInformListener(); + } +} + +void InvalidationClientCore::FinishStartingTiclAndInformListener() { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + CHECK(!ticl_state_.IsStarted()); + + ticl_state_.Start(); + GetListener()->Ready(this); + + // We are not currently persisting our registration digest, so regardless of + // whether or not we are restarting from persistent state, we need to query + // the application for all of its registrations. + GetListener()->ReissueRegistrations(this, + RegistrationManager::kEmptyPrefix, 0); + TLOG(logger_, INFO, "Ticl started: %s", ToString().c_str()); +} + +void InvalidationClientCore::ScheduleStartAfterReadingStateBlob() { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + storage_->ReadKey(kClientTokenKey, + NewPermanentCallback(this, &InvalidationClientCore::ReadCallback)); +} + +void InvalidationClientCore::ReadCallback( + pair<Status, string> read_result) { + string serialized_state; + if (read_result.first.IsSuccess()) { + serialized_state = read_result.second; + } else { + statistics_->RecordError( + Statistics::ClientErrorType_PERSISTENT_READ_FAILURE); + TLOG(logger_, WARNING, "Could not read state blob: %s", + read_result.first.message().c_str()); + } + // Call start now. + internal_scheduler_->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + this, &InvalidationClientCore::StartInternal, serialized_state)); +} + +ExponentialBackoffDelayGenerator* +InvalidationClientCore::CreateExpBackOffGenerator( + const TimeDelta& initial_delay) { + return new ExponentialBackoffDelayGenerator(random_.get(), initial_delay, + config_.max_exponential_backoff_factor()); +} + +InvalidationListener::RegistrationState +InvalidationClientCore::ConvertOpTypeToRegState(RegistrationP::OpType + reg_op_type) { + InvalidationListener::RegistrationState reg_state = + reg_op_type == RegistrationP_OpType_REGISTER ? + InvalidationListener::REGISTERED : + InvalidationListener::UNREGISTERED; + return reg_state; +} + +void InvalidationClientCore::MessageReceiver(string message) { + internal_scheduler_->Schedule(Scheduler::NoDelay(), NewPermanentCallback( + this, + &InvalidationClientCore::HandleIncomingMessage, message)); +} + +void InvalidationClientCore::NetworkStatusReceiver(bool status) { + internal_scheduler_->Schedule(Scheduler::NoDelay(), NewPermanentCallback( + this, &InvalidationClientCore::HandleNetworkStatusChange, status)); +} + + +void InvalidationClientCore::ChangeNetworkTimeoutDelayForTest( + const TimeDelta& delay) { + config_.set_network_timeout_delay_ms(delay.InMilliseconds()); + CreateSchedulingTasks(); +} + +void InvalidationClientCore::ChangeHeartbeatDelayForTest( + const TimeDelta& delay) { + config_.set_heartbeat_interval_ms(delay.InMilliseconds()); + CreateSchedulingTasks(); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-core.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-core.h new file mode 100644 index 0000000..d6f66d7 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-core.h @@ -0,0 +1,490 @@ +// Copyright 2012 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. + +// Implementation of the Invalidation Client Library (Ticl). + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_CORE_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_CORE_H_ + +#include <string> +#include <utility> + +#include "google/cacheinvalidation/include/invalidation-client.h" +#include "google/cacheinvalidation/include/invalidation-listener.h" +#include "google/cacheinvalidation/deps/digest-function.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" +#include "google/cacheinvalidation/impl/digest-store.h" +#include "google/cacheinvalidation/impl/exponential-backoff-delay-generator.h" +#include "google/cacheinvalidation/impl/protocol-handler.h" +#include "google/cacheinvalidation/impl/registration-manager.h" +#include "google/cacheinvalidation/impl/run-state.h" +#include "google/cacheinvalidation/impl/safe-storage.h" +#include "google/cacheinvalidation/impl/smearer.h" + +namespace invalidation { + +class InvalidationClientCore; + +/* A task for acquiring tokens from the server. */ +class AcquireTokenTask : public RecurringTask { + public: + explicit AcquireTokenTask(InvalidationClientCore* client); + virtual ~AcquireTokenTask() {} + + // The actual implementation as required by the RecurringTask. + virtual bool RunTask(); + + private: + /* The client that owns this task. */ + InvalidationClientCore* client_; +}; + +/* A task that schedules heartbeats when the registration summary at the client + * is not in sync with the registration summary from the server. + */ +class RegSyncHeartbeatTask : public RecurringTask { + public: + explicit RegSyncHeartbeatTask(InvalidationClientCore* client); + virtual ~RegSyncHeartbeatTask() {} + + // The actual implementation as required by the RecurringTask. + virtual bool RunTask(); + + private: + /* The client that owns this task. */ + InvalidationClientCore* client_; +}; + +/* A task that writes the token to persistent storage. */ +class PersistentWriteTask : public RecurringTask { + public: + explicit PersistentWriteTask(InvalidationClientCore* client); + virtual ~PersistentWriteTask() {} + + // The actual implementation as required by the RecurringTask. + virtual bool RunTask(); + + private: + /* Handles the result of a request to write to persistent storage. + * |state| is the serialized state that was written. + */ + void WriteCallback(const string& state, Status status); + + InvalidationClientCore* client_; + + /* The last client token that was written to to persistent state + * successfully. + */ + string last_written_token_; +}; + +/* A task for sending heartbeats to the server. */ +class HeartbeatTask : public RecurringTask { + public: + explicit HeartbeatTask(InvalidationClientCore* client); + virtual ~HeartbeatTask() {} + + // The actual implementation as required by the RecurringTask. + virtual bool RunTask(); + private: + /* The client that owns this task. */ + InvalidationClientCore* client_; + + /* Next time that the performance counters are sent to the server. */ + Time next_performance_send_time_; +}; + +/* The task that is scheduled to send batched messages to the server (when + * needed). + */ +class BatchingTask : public RecurringTask { + public: + BatchingTask(ProtocolHandler *handler, Smearer* smearer, + TimeDelta batching_delay); + + virtual ~BatchingTask() {} + + // The actual implementation as required by the RecurringTask. + virtual bool RunTask(); + + private: + ProtocolHandler* protocol_handler_; +}; + +class InvalidationClientCore : public InvalidationClient, + public ProtocolListener { + public: + /* Modifies |config| to contain default parameters. */ + static void InitConfig(ClientConfigP *config); + + /* Modifies |config| to contain parameters set for unit tests. */ + static void InitConfigForTest(ClientConfigP *config); + + /* Creates the tasks used by the Ticl for token acquisition, heartbeats, + * persistent writes and registration sync. + */ + void CreateSchedulingTasks(); + + /* Stores the client id that is used for squelching invalidations on the + * server side. + */ + void GetApplicationClientIdForTest(string* result) { + application_client_id_.SerializeToString(result); + } + + void GetClientTokenForTest(string* result) { + *result = client_token_; + } + + // Getters for testing. No transfer of ownership occurs in any of these + // methods. + + /* Returns the system resources. */ + SystemResources* GetResourcesForTest() { + return resources_; + } + + /* Returns the performance counters/statistics. */ + Statistics* GetStatisticsForTest() { + return statistics_.get(); + } + + /* Returns the digest function used for computing digests for object + * registrations. + */ + DigestFunction* GetDigestFunctionForTest() { + return digest_fn_.get(); + } + + /* Changes the existing delay for the network timeout delay in the operation + * scheduler to be delay_ms. + */ + void ChangeNetworkTimeoutDelayForTest(const TimeDelta& delay); + + /* Changes the existing delay for the heartbeat delay in the operation + * scheduler to be delay_ms. + */ + void ChangeHeartbeatDelayForTest(const TimeDelta& delay); + + /* Returns the next time a message is allowed to be sent to the server (could + * be in the past). + */ + int64 GetNextMessageSendTimeMsForTest() { + return protocol_handler_.GetNextMessageSendTimeMsForTest(); + } + + /* Returns true iff the client is currently started. */ + bool IsStartedForTest() { + return ticl_state_.IsStarted(); + } + + /* Sets the digest store to be digest_store for testing purposes. + * + * REQUIRES: This method is called before the Ticl has been started. + */ + void SetDigestStoreForTest(DigestStore<ObjectIdP>* digest_store) { + CHECK(!resources_->IsStarted()); + registration_manager_.SetDigestStoreForTest(digest_store); + } + + virtual void Start(); + + virtual void Stop(); + + virtual void Register(const ObjectId& object_id); + + virtual void Unregister(const ObjectId& object_id); + + virtual void Register(const vector<ObjectId>& object_ids) { + PerformRegisterOperations(object_ids, RegistrationP_OpType_REGISTER); + } + + virtual void Unregister(const vector<ObjectId>& object_ids) { + PerformRegisterOperations(object_ids, RegistrationP_OpType_UNREGISTER); + } + + /* Implementation of (un)registration. + * + * Arguments: + * object_ids - object ids on which to operate + * reg_op_type - whether to register or unregister + */ + virtual void PerformRegisterOperations( + const vector<ObjectId>& object_ids, RegistrationP::OpType reg_op_type); + + void PerformRegisterOperationsInternal( + const vector<ObjectId>& object_ids, RegistrationP::OpType reg_op_type); + + virtual void Acknowledge(const AckHandle& acknowledge_handle); + + string ToString(); + + /* Returns a randomly generated nonce. */ + static string GenerateNonce(Random* random); + + // + // Protocol listener methods + // + + /* Returns the current client token. */ + virtual string GetClientToken(); + + virtual void GetRegistrationSummary(RegistrationSummary* summary) { + registration_manager_.GetClientSummary(summary); + } + + virtual void HandleMessageSent(); + + /* Gets registration manager state as a serialized RegistrationManagerState. + */ + void GetRegistrationManagerStateAsSerializedProto(string* result); + + /* Gets statistics as a serialized InfoMessage. */ + void GetStatisticsAsSerializedProto(string* result); + + /* The single key used to write all the Ticl state. */ + static const char* kClientTokenKey; + protected: + /* Constructs a client. + * + * Arguments: + * resources - resources to use during execution + * random - a random number generator (owned by this after the call) + * client_type - client type code + * client_name - application identifier for the client + * config - configuration for the client + */ + InvalidationClientCore( + SystemResources* resources, Random* random, int client_type, + const string& client_name, const ClientConfigP &config, + const string& application_name); + + /* Returns the internal scheduler. */ + Scheduler* GetInternalScheduler() { + return internal_scheduler_; + } + + /* Returns the statistics. */ + Statistics* GetStatistics() { + return statistics_.get(); + } + + /* Returns the listener. */ + virtual InvalidationListener* GetListener() = 0; + private: + // Friend classes so that they can access the scheduler, logger, smearer, etc. + friend class AcquireTokenTask; + friend class HeartbeatTask; + friend class InvalidationClientFactoryTest; + friend class PersistentWriteTask; + friend class RegSyncHeartbeatTask; + + // + // Private methods. + // + + virtual void HandleNetworkStatusChange(bool is_online); + + /* Implementation of start on the internal thread with the persistent + * serialized_state if any. Starts the TICL protocol and makes the TICL ready + * to received registration, invalidations, etc + */ + void StartInternal(const string& serialized_state); + + void AcknowledgeInternal(const AckHandle& acknowledge_handle); + + /* Set client_token to NULL and schedule acquisition of the token. */ + void ScheduleAcquireToken(const string& debug_string); + + /* Sends an info message to the server. If mustSendPerformanceCounters is + * true, the performance counters are sent regardless of when they were sent + * earlier. + */ + void SendInfoMessageToServer( + bool mustSendPerformanceCounters, bool request_server_summary); + + /* Sets the nonce to new_nonce. + * + * REQUIRES: new_nonce be empty or client_token_ be empty. The goal is to + * ensure that a nonce is never set unless there is no client token, unless + * the nonce is being cleared. + */ + void set_nonce(const string& new_nonce); + + /* Sets the client_token_ to new_client_token. + * + * REQUIRES: new_client_token be empty or nonce_ be empty. The goal is to + * ensure that a token is never set unless there is no nonce, unless the token + * is being cleared. + */ + void set_client_token(const string& new_client_token); + + /* Reads the Ticl state from persistent storage (if any) and calls + * startInternal. + */ + void ScheduleStartAfterReadingStateBlob(); + + /* Handles the result of a request to read from persistent storage. */ + void ReadCallback(pair<Status, string> read_result); + + /* Finish starting the ticl and inform the listener that it is ready. */ + void FinishStartingTiclAndInformListener(); + + /* Returns an exponential backoff generator with a max exponential factor + * given by |config_.max_exponential_backoff_factor| and initial delay + * |initial_delay|. + * Space for the returned object is owned by the caller. + */ + ExponentialBackoffDelayGenerator* CreateExpBackOffGenerator( + const TimeDelta& initial_delay); + + /* Registers a message receiver and status change listener on |resources|. */ + void RegisterWithNetwork(SystemResources* resources); + + /* Handles inbound messages from the network. */ + void MessageReceiver(string message); + + /* Responds to changes in network connectivity. */ + void NetworkStatusReceiver(bool status); + + /* Handles a |message| from the server. */ + void HandleIncomingMessage(const string& message); + + /* + * Handles a changed token. |header_token| is the token in the server message + * header. |new_token| is a new token from the server; if empty, it indicates + * a destroy-token message. + */ + void HandleTokenChanged(const string& header_token, const string& new_token); + + /* Processes a server message |header|. */ + void HandleIncomingHeader(const ServerMessageHeader& header); + + /* Handles |invalidations| from the server. */ + void HandleInvalidations( + const RepeatedPtrField<InvalidationP>& invalidations); + + /* Handles registration statusES from the server. */ + void HandleRegistrationStatus( + const RepeatedPtrField<RegistrationStatus>& reg_status_list); + + /* Handles A registration sync request from the server. */ + void HandleRegistrationSyncRequest(); + + /* Handles an info message request from the server. */ + void HandleInfoMessage( + const RepeatedField<InfoRequestMessage_InfoType>& info_types); + + /* Handles an error message with |code| and |description| from the server. */ + void HandleErrorMessage(ErrorMessage::Code code, const string& description); + + /* Returns whether |server_token| matches the client token or nonce. */ + bool ValidateToken(const string& server_token); + + /* Converts an operation type reg_status to a + * InvalidationListener::RegistrationState. + */ + static InvalidationListener::RegistrationState ConvertOpTypeToRegState( + RegistrationP::OpType reg_op_type); + + /* Resources for the Ticl. */ + SystemResources* resources_; // Owned by application. + + /* Reference into the resources object for cleaner code. All Ticl code must be + * scheduled on this scheduler. + */ + Scheduler* internal_scheduler_; + + /* Logger reference into the resources object for cleaner code. */ + Logger* logger_; + + /* A storage layer which schedules the callbacks on the internal scheduler + * thread. + */ + scoped_ptr<SafeStorage> storage_; + + /* Statistics objects to track number of sent messages, etc. */ + scoped_ptr<Statistics> statistics_; + + /* Configuration for this instance. */ + ClientConfigP config_; + + /* Application identifier for this client. */ + ApplicationClientIdP application_client_id_; + + /* The function for computing the registration and persistence state digests. + */ + scoped_ptr<DigestFunction> digest_fn_; + + /* Object maintaining the registration state for this client. */ + RegistrationManager registration_manager_; + + /* Used to validate messages */ + scoped_ptr<TiclMessageValidator> msg_validator_; + + /* A smearer to make sure that delays are randomized a little bit. */ + Smearer smearer_; + + /* Object handling low-level wire format interactions. */ + ProtocolHandler protocol_handler_; + + /* The state of the Ticl whether it has started or not. */ + RunState ticl_state_; + + /* Current client token known from the server. */ + string client_token_; + + // After the client starts, exactly one of nonce and clientToken is non-null. + + /* If not empty, nonce for pending identifier request. */ + string nonce_; + + /* Whether we should send registrations to the server or not. */ + // TODO(ghc): [cleanup] Make the server summary in the registration manager + // nullable and replace this variable with a test for whether it's null or + // not. + bool should_send_registrations_; + + /* Whether the network is online. Assume so when we start. */ + bool is_online_; + + /* Last time a message was sent to the server. */ + Time last_message_send_time_; + + /* A task for acquiring the token (if the client has no token). */ + scoped_ptr<AcquireTokenTask> acquire_token_task_; + + /* Task for checking if reg summary is out of sync and then sending a + * heartbeat to the server. + */ + scoped_ptr<RegSyncHeartbeatTask> reg_sync_heartbeat_task_; + + /* Task for writing the state blob to persistent storage. */ + scoped_ptr<PersistentWriteTask> persistent_write_task_; + + /* A task for periodic heartbeats. */ + scoped_ptr<HeartbeatTask> heartbeat_task_; + + /* Task to send all batched messages to the server. */ + scoped_ptr<BatchingTask> batching_task_; + + /* Random number generator for smearing, exp backoff, etc. */ + scoped_ptr<Random> random_; + + DISALLOW_COPY_AND_ASSIGN(InvalidationClientCore); +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_CORE_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-factory.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-factory.cc new file mode 100644 index 0000000..996a459 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-factory.cc @@ -0,0 +1,76 @@ +// Copyright 2012 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. + +#include "google/cacheinvalidation/include/invalidation-client-factory.h" + +#include "google/cacheinvalidation/impl/invalidation-client-impl.h" + +namespace invalidation { + +InvalidationClient* ClientFactory::Create( + SystemResources* resources, + const InvalidationClientConfig& config, + InvalidationListener* listener) { + ClientConfigP client_config; + InvalidationClientCore::InitConfig(&client_config); + client_config.set_allow_suppression(config.allow_suppression()); + Random* random = new Random(InvalidationClientUtil::GetCurrentTimeMs( + resources->internal_scheduler())); + return new InvalidationClientImpl( + resources, random, config.client_type(), config.client_name(), + client_config, config.application_name(), listener); +} + +// Deprecated, please the factory function that takes an +// InvalidationClientConfig instead. +InvalidationClient* CreateInvalidationClient( + SystemResources* resources, + int32 client_type, + const string& client_name, + const string& application_name, + InvalidationListener* listener) { + InvalidationClientConfig config( + client_type, client_name, application_name, true /* allowSuppression*/); + return ClientFactory::Create(resources, config, listener); +} + +InvalidationClient* ClientFactory::CreateForTest( + SystemResources* resources, + const InvalidationClientConfig& config, + InvalidationListener* listener) { + // Make a config with test params and construct an instance to return. + ClientConfigP client_config; + InvalidationClientCore::InitConfigForTest(&client_config); + client_config.set_allow_suppression(config.allow_suppression()); + Random* random = new Random(InvalidationClientUtil::GetCurrentTimeMs( + resources->internal_scheduler())); + return new InvalidationClientImpl( + resources, random, config.client_type(), config.client_name(), + client_config, config.application_name(), listener); +} + +InvalidationClient* CreateInvalidationClientForTest( + SystemResources* resources, + int32 client_type, + const string& client_name, + const string& application_name, + InvalidationListener* listener) { + return ClientFactory::CreateForTest( + resources, + InvalidationClientConfig(client_type, client_name, application_name, + true /* allowSuppression */), + listener); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-factory_test.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-factory_test.cc new file mode 100644 index 0000000..ea51664 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-factory_test.cc @@ -0,0 +1,114 @@ +// Copyright 2013 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. + +// Unit tests for the InvalidationClientFactory class. + +#include "google/cacheinvalidation/include/invalidation-client-factory.h" +#include "google/cacheinvalidation/include/invalidation-listener.h" +#include "google/cacheinvalidation/include/types.h" +#include "google/cacheinvalidation/types.pb.h" +#include "google/cacheinvalidation/deps/googletest.h" +#include "google/cacheinvalidation/impl/basic-system-resources.h" +#include "google/cacheinvalidation/impl/constants.h" +#include "google/cacheinvalidation/impl/invalidation-client-impl.h" + +#include "google/cacheinvalidation/test/test-utils.h" + +namespace invalidation { + +using ::ipc::invalidation::ClientType_Type_TEST; +using ::ipc::invalidation::ObjectSource_Type_TEST; +using ::testing::StrictMock; + +// Test constants +static const char CLIENT_NAME[] = "demo-client-01"; +static const char APPLICATION_NAME[] = "demo-app"; + +// Tests the basic functionality of the invalidation client factory. +class InvalidationClientFactoryTest : public UnitTestBase { + public: + virtual ~InvalidationClientFactoryTest() {} + + // Performs setup for client factory unit tests, e.g. creating resource + // components and setting up common expectations for certain mock objects. + virtual void SetUp() { + UnitTestBase::SetUp(); + InitCommonExpectations(); // Set up expectations for common mock operations + + // Set up the listener scheduler to run any runnable that it receives. + EXPECT_CALL(*listener_scheduler, Schedule(_, _)) + .WillRepeatedly(InvokeAndDeleteClosure<1>()); + } + + // Creates a client with the given value for allowSuppression. + // The caller owns the storage. + InvalidationClientImpl* CreateClient(bool allowSuppression) { + InvalidationClientConfig config(ClientType_Type_TEST, + CLIENT_NAME, APPLICATION_NAME, allowSuppression); + return static_cast<InvalidationClientImpl*>( + ClientFactory::Create(resources.get(), config, &listener)); + } + + // Verifies that a client has expected values for allowing suppression + // and application client id. + void CheckClientValid(const InvalidationClientImpl* client, + bool allowSuppression) { + // Check that the the allow suppression flag was correctly set to + // the expected value. + ClientConfigP config = client->config_; + ASSERT_EQ(allowSuppression, config.allow_suppression()); + + // Check that the client type and client name were properly populated. + ASSERT_EQ(ClientType_Type_TEST, + client->application_client_id_.client_type()); + + ASSERT_EQ(CLIENT_NAME, + client->application_client_id_.client_name()); + } + + // The client being tested. Created fresh for each test function. + scoped_ptr<InvalidationClientImpl> client; + + // A mock invalidation listener. + StrictMock<MockInvalidationListener> listener; +}; + +// Tests that the deprecated CreateInvalidationClient overload +// correctly initializes the client to allow suppression. +TEST_F(InvalidationClientFactoryTest, TestCreateClient) { + client.reset(static_cast<InvalidationClientImpl*>( + CreateInvalidationClient( + resources.get(), + ClientType_Type_TEST, + CLIENT_NAME, + APPLICATION_NAME, + &listener))); + CheckClientValid(client.get(), true /* allowSuppression */); +} + +// Tests CreateClient with allowSuppression = false. +TEST_F(InvalidationClientFactoryTest, TestCreateClientForTrickles) { + bool allowSuppression = false; + client.reset(CreateClient(allowSuppression)); + CheckClientValid(client.get(), allowSuppression); +} + +// Tests CreateClient with allowSuppression = true. +TEST_F(InvalidationClientFactoryTest, testCreateClientForInvalidation) { + bool allowSuppression = true; + client.reset(CreateClient(allowSuppression)); + CheckClientValid(client.get(), allowSuppression); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl.cc new file mode 100644 index 0000000..947ffe4 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl.cc @@ -0,0 +1,79 @@ +// Copyright 2012 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. + +// Implementation of the Invalidation Client Library (Ticl). + +#include "google/cacheinvalidation/impl/invalidation-client-impl.h" + +namespace invalidation { + +InvalidationClientImpl::InvalidationClientImpl( + SystemResources* resources, Random* random, int client_type, + const string& client_name, const ClientConfigP& config, + const string& application_name, InvalidationListener* listener) + : InvalidationClientCore(resources, random, client_type, client_name, + config, application_name), + listener_(new CheckingInvalidationListener( + listener, GetStatistics(), resources->internal_scheduler(), + resources->listener_scheduler(), resources->logger())) { +} + +void InvalidationClientImpl::Start() { + GetInternalScheduler()->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback(this, &InvalidationClientImpl::DoStart)); +} + +void InvalidationClientImpl::Stop() { + GetInternalScheduler()->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback(this, &InvalidationClientImpl::DoStop)); +} + +void InvalidationClientImpl::Register(const ObjectId& object_id) { + GetInternalScheduler()->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback(this, &InvalidationClientImpl::DoRegister, + object_id)); +} + +void InvalidationClientImpl::Register(const vector<ObjectId>& object_ids) { + GetInternalScheduler()->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback(this, &InvalidationClientImpl::DoBulkRegister, + object_ids)); +} + +void InvalidationClientImpl::Unregister(const ObjectId& object_id) { + GetInternalScheduler()->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback(this, &InvalidationClientImpl::DoUnregister, + object_id)); +} + +void InvalidationClientImpl::Unregister(const vector<ObjectId>& object_ids) { + GetInternalScheduler()->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback(this, &InvalidationClientImpl::DoBulkUnregister, + object_ids)); +} + +void InvalidationClientImpl::Acknowledge(const AckHandle& acknowledge_handle) { + GetInternalScheduler()->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback(this, &InvalidationClientImpl::DoAcknowledge, + acknowledge_handle)); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl.h new file mode 100644 index 0000000..2321dcc --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl.h @@ -0,0 +1,122 @@ +// Copyright 2012 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. + +// Implementation of the Invalidation Client Library (Ticl). + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_IMPL_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_IMPL_H_ + +#include <string> +#include <utility> + +#include "google/cacheinvalidation/include/invalidation-client.h" +#include "google/cacheinvalidation/include/invalidation-listener.h" +#include "google/cacheinvalidation/impl/checking-invalidation-listener.h" +#include "google/cacheinvalidation/impl/invalidation-client-core.h" +#include "google/cacheinvalidation/impl/protocol-handler.h" + +namespace invalidation { + +class InvalidationClientImpl : public InvalidationClientCore { + public: + /* Constructs a client. + * + * Arguments: + * resources - resources to use during execution + * random - a random number generator (owned by this after the call) + * client_type - client type code + * client_name - application identifier for the client + * config - configuration for the client + * listener - application callback + */ + InvalidationClientImpl( + SystemResources* resources, Random* random, int client_type, + const string& client_name, const ClientConfigP &config, + const string& application_name, InvalidationListener* listener); + + // These methods override those in InvalidationClientCore. Their + // implementations all enqueue an event onto the work queue and + // then delegate to the InvalidationClientCore method through one + // of the private DoYYY functions (below). + + virtual void Start(); + + virtual void Stop(); + + virtual void Register(const ObjectId& object_id); + + virtual void Unregister(const ObjectId& object_id); + + virtual void Register(const vector<ObjectId>& object_ids); + + virtual void Unregister(const vector<ObjectId>& object_ids); + + virtual void Acknowledge(const AckHandle& acknowledge_handle); + + /* Returns the listener that was registered by the caller. */ + InvalidationListener* GetInvalidationListenerForTest() { + return listener_.get()->delegate(); + } + + protected: + virtual InvalidationListener* GetListener() { + return listener_.get(); + } + + private: + /* + * All of these methods simply delegate to the superclass implementation. They + * exist so that NewPermanentCallback objects created in + * invalidation-client-impl.cc can call superclass methods. + */ + void DoStart() { + this->InvalidationClientCore::Start(); + } + + void DoStop() { + this->InvalidationClientCore::Stop(); + } + + void DoRegister(const ObjectId& object_id) { + this->InvalidationClientCore::Register(object_id); + } + + void DoUnregister(const ObjectId& object_id) { + this->InvalidationClientCore::Unregister(object_id); + } + + void DoBulkRegister(const vector<ObjectId>& object_ids) { + this->InvalidationClientCore::Register(object_ids); + } + + void DoBulkUnregister(const vector<ObjectId>& object_ids) { + this->InvalidationClientCore::Unregister(object_ids); + } + + void DoAcknowledge(const AckHandle& acknowledge_handle) { + this->InvalidationClientCore::Acknowledge(acknowledge_handle); + } + + /* + * The listener registered by the application, wrapped in a + * CheckingInvalidationListener. + */ + scoped_ptr<CheckingInvalidationListener> listener_; + + DISALLOW_COPY_AND_ASSIGN(InvalidationClientImpl); +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_IMPL_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl_test.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl_test.cc new file mode 100644 index 0000000..ab72ef2 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl_test.cc @@ -0,0 +1,504 @@ +// Copyright 2012 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. + +// Unit tests for the InvalidationClientImpl class. + +#include <vector> + +#include "google/cacheinvalidation/client_test_internal.pb.h" +#include "google/cacheinvalidation/types.pb.h" +#include "google/cacheinvalidation/include/invalidation-listener.h" +#include "google/cacheinvalidation/include/types.h" +#include "google/cacheinvalidation/deps/gmock.h" +#include "google/cacheinvalidation/deps/googletest.h" +#include "google/cacheinvalidation/deps/random.h" +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/impl/basic-system-resources.h" +#include "google/cacheinvalidation/impl/constants.h" +#include "google/cacheinvalidation/impl/invalidation-client-impl.h" +#include "google/cacheinvalidation/impl/statistics.h" +#include "google/cacheinvalidation/impl/throttle.h" +#include "google/cacheinvalidation/impl/ticl-message-validator.h" +#include "google/cacheinvalidation/test/deterministic-scheduler.h" +#include "google/cacheinvalidation/test/test-logger.h" +#include "google/cacheinvalidation/test/test-utils.h" + +namespace invalidation { + +using ::ipc::invalidation::ClientType_Type_TEST; +using ::ipc::invalidation::RegistrationManagerStateP; +using ::ipc::invalidation::ObjectSource_Type_TEST; +using ::ipc::invalidation::StatusP_Code_PERMANENT_FAILURE; +using ::testing::_; +using ::testing::AllOf; +using ::testing::DeleteArg; +using ::testing::DoAll; +using ::testing::ElementsAre; +using ::testing::EqualsProto; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::InvokeArgument; +using ::testing::Matcher; +using ::testing::Property; +using ::testing::Return; +using ::testing::ReturnPointee; +using ::testing::SaveArg; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::proto::WhenDeserializedAs; + +// Creates an action SaveArgToVector<k>(vector*) that saves the kth argument in +// |vec|. +ACTION_TEMPLATE( + SaveArgToVector, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_1_VALUE_PARAMS(vec)) { + vec->push_back(std::tr1::get<k>(args)); +} + +// Given the ReadCallback of Storage::ReadKey as argument 1, invokes it with a +// permanent failure status code. +ACTION(InvokeReadCallbackFailure) { + arg1->Run(pair<Status, string>(Status(Status::PERMANENT_FAILURE, ""), "")); + delete arg1; +} + +// Given the WriteCallback of Storage::WriteKey as argument 2, invokes it with +// a success status code. +ACTION(InvokeWriteCallbackSuccess) { + arg2->Run(Status(Status::SUCCESS, "")); + delete arg2; +} + +// Tests the basic functionality of the invalidation client. +class InvalidationClientImplTest : public UnitTestBase { + public: + virtual ~InvalidationClientImplTest() {} + + // Performs setup for protocol handler unit tests, e.g. creating resource + // components and setting up common expectations for certain mock objects. + virtual void SetUp() { + UnitTestBase::SetUp(); + InitCommonExpectations(); // Set up expectations for common mock operations + + + // Clear throttle limits so that it does not interfere with any test. + InvalidationClientImpl::InitConfig(&config); + config.set_smear_percent(kDefaultSmearPercent); + config.mutable_protocol_handler_config()->clear_rate_limit(); + + // Set up the listener scheduler to run any runnable that it receives. + EXPECT_CALL(*listener_scheduler, Schedule(_, _)) + .WillRepeatedly(InvokeAndDeleteClosure<1>()); + + // Create the actual client. + Random* random = new Random(InvalidationClientUtil::GetCurrentTimeMs( + resources->internal_scheduler())); + client.reset(new InvalidationClientImpl( + resources.get(), random, ClientType_Type_TEST, "clientName", config, + "InvClientTest", &listener)); + } + + // Starts the Ticl and ensures that the initialize message is sent. In + // response, gives a tokencontrol message to the protocol handler and makes + // sure that ready is called. client_messages is the list of messages expected + // from the client. The 0th message corresponds to the initialization message + // sent out by the client. + void StartClient() { + // Start the client. + client.get()->Start(); + + // Let the message be sent out. + internal_scheduler->PassTime( + GetMaxBatchingDelay(config.protocol_handler_config())); + + // Check that the message contains an initializeMessage. + ClientToServerMessage client_message; + client_message.ParseFromString(outgoing_messages[0]); + ASSERT_TRUE(client_message.has_initialize_message()); + string nonce = client_message.initialize_message().nonce(); + + // Create the token control message and hand it to the protocol handler. + ServerToClientMessage sc_message; + InitServerHeader(nonce, sc_message.mutable_header()); + string new_token = "new token"; + sc_message.mutable_token_control_message()->set_new_token(new_token); + ProcessIncomingMessage(sc_message, MessageHandlingDelay()); + } + + // Sets the expectations so that the Ticl is ready to be started such that + // |num_outgoing_messages| are expected to be sent by the ticl. These messages + // will be saved in |outgoing_messages|. + void SetExpectationsForTiclStart(int num_outgoing_msgs) { + // Set up expectations for number of messages expected on the network. + EXPECT_CALL(*network, SendMessage(_)) + .Times(num_outgoing_msgs) + .WillRepeatedly(SaveArgToVector<0>(&outgoing_messages)); + + // Expect the storage to perform a read key that we will fail. + EXPECT_CALL(*storage, ReadKey(_, _)) + .WillOnce(InvokeReadCallbackFailure()); + + // Expect the listener to indicate that it is ready and let it reissue + // registrations. + EXPECT_CALL(listener, Ready(Eq(client.get()))); + EXPECT_CALL(listener, ReissueRegistrations(Eq(client.get()), _, _)); + + // Expect the storage layer to receive the write of the session token. + EXPECT_CALL(*storage, WriteKey(_, _, _)) + .WillOnce(InvokeWriteCallbackSuccess()); + } + + // + // Test state maintained for every test. + // + + // Messages sent by the Ticl. + vector<string> outgoing_messages; + + // Configuration for the protocol handler (uses defaults). + ClientConfigP config; + + // The client being tested. Created fresh for each test function. + scoped_ptr<InvalidationClientImpl> client; + + // A mock invalidation listener. + StrictMock<MockInvalidationListener> listener; +}; + +// Starts the ticl and checks that appropriate calls are made on the listener +// and that a proper message is sent on the network. +TEST_F(InvalidationClientImplTest, Start) { + SetExpectationsForTiclStart(1); + StartClient(); +} + +// Tests that GenerateNonce generates a unique nonce on every call. +TEST_F(InvalidationClientImplTest, GenerateNonce) { + // Create a random number generated seeded with the current time. + scoped_ptr<Random> random; + random.reset(new Random(InvalidationClientUtil::GetCurrentTimeMs( + resources->internal_scheduler()))); + + // Generate two nonces and make sure they are distinct. (The chances + // of a collision should be vanishingly small since our correctness + // relies upon no collisions.) + string nonce1 = InvalidationClientCore::GenerateNonce(random.get()); + string nonce2 = InvalidationClientCore::GenerateNonce(random.get()); + ASSERT_NE(nonce1, nonce2); +} + +// Starts the Ticl, registers for a few objects, gets success and ensures that +// the right listener methods are invoked. +TEST_F(InvalidationClientImplTest, Register) { + SetExpectationsForTiclStart(2); + + // Set some expectations for registration status messages. + vector<ObjectId> saved_oids; + EXPECT_CALL(listener, + InformRegistrationStatus(Eq(client.get()), _, + InvalidationListener::REGISTERED)) + .Times(3) + .WillRepeatedly(SaveArgToVector<1>(&saved_oids)); + + // Start the Ticl. + StartClient(); + + // Synthesize a few test object ids. + int num_objects = 3; + vector<ObjectIdP> oid_protos; + vector<ObjectId> oids; + InitTestObjectIds(num_objects, &oid_protos); + ConvertFromObjectIdProtos(oid_protos, &oids); + + // Register + client.get()->Register(oids); + + // Let the message be sent out. + internal_scheduler->PassTime( + GetMaxBatchingDelay(config.protocol_handler_config())); + + // Give a registration status message to the protocol handler and wait for + // the listener calls. + ServerToClientMessage message; + InitServerHeader(client.get()->GetClientToken(), message.mutable_header()); + vector<RegistrationStatus> registration_statuses; + MakeRegistrationStatusesFromObjectIds(oid_protos, true, true, + ®istration_statuses); + for (int i = 0; i < num_objects; ++i) { + message.mutable_registration_status_message() + ->add_registration_status()->CopyFrom(registration_statuses[i]); + } + + // Give this message to the protocol handler. + ProcessIncomingMessage(message, EndOfTestWaitTime()); + + // Check the object ids. + ASSERT_TRUE(CompareVectorsAsSets(saved_oids, oids)); + + // Check the registration message. + ClientToServerMessage client_msg; + client_msg.ParseFromString(outgoing_messages[1]); + ASSERT_TRUE(client_msg.has_registration_message()); + ASSERT_FALSE(client_msg.has_info_message()); + ASSERT_FALSE(client_msg.has_registration_sync_message()); + + RegistrationMessage expected_msg; + InitRegistrationMessage(oid_protos, true, &expected_msg); + const RegistrationMessage& actual_msg = client_msg.registration_message(); + ASSERT_TRUE(CompareMessages(expected_msg, actual_msg)); +} + +// Tests that given invalidations from the server, the right listener methods +// are invoked. Ack the invalidations and make sure that the ack message is sent +// out. Include a payload in one invalidation and make sure the client does not +// include it in the ack. +TEST_F(InvalidationClientImplTest, Invalidations) { + // Set some expectations for starting the client. + SetExpectationsForTiclStart(2); + + // Synthesize a few test object ids. + int num_objects = 3; + vector<ObjectIdP> oid_protos; + vector<ObjectId> oids; + InitTestObjectIds(num_objects, &oid_protos); + ConvertFromObjectIdProtos(oid_protos, &oids); + + // Set up listener invalidation calls. + vector<InvalidationP> invalidations; + vector<Invalidation> expected_invs; + MakeInvalidationsFromObjectIds(oid_protos, &invalidations); + // Put a payload in one of the invalidations. + invalidations[0].set_payload("this is a payload"); + ConvertFromInvalidationProtos(invalidations, &expected_invs); + + // Set up expectations for the acks. + vector<Invalidation> saved_invs; + vector<AckHandle> ack_handles; + + EXPECT_CALL(listener, Invalidate(Eq(client.get()), _, _)) + .Times(3) + .WillRepeatedly(DoAll(SaveArgToVector<1>(&saved_invs), + SaveArgToVector<2>(&ack_handles))); + + // Start the Ticl. + StartClient(); + + // Give this message to the protocol handler. + ServerToClientMessage message; + InitServerHeader(client.get()->GetClientToken(), message.mutable_header()); + InitInvalidationMessage(invalidations, + message.mutable_invalidation_message()); + + // Process the incoming invalidation message. + ProcessIncomingMessage(message, MessageHandlingDelay()); + + // Check the invalidations. + ASSERT_TRUE(CompareVectorsAsSets(expected_invs, saved_invs)); + + // Ack the invalidations now and wait for them to be sent out. + for (int i = 0; i < num_objects; i++) { + client.get()->Acknowledge(ack_handles[i]); + } + internal_scheduler->PassTime( + GetMaxBatchingDelay(config.protocol_handler_config())); + + // Check that the ack message is as expected. + ClientToServerMessage client_msg; + client_msg.ParseFromString(outgoing_messages[1]); + ASSERT_TRUE(client_msg.has_invalidation_ack_message()); + + InvalidationMessage expected_msg; + // The client should strip the payload from the invalidation. + invalidations[0].clear_payload(); + InitInvalidationMessage(invalidations, &expected_msg); + const InvalidationMessage& actual_msg = + client_msg.invalidation_ack_message(); + ASSERT_TRUE(CompareMessages(expected_msg, actual_msg)); +} + +// Give a registration sync request message and an info request message to the +// client and wait for the sync message and the info message to go out. +TEST_F(InvalidationClientImplTest, ServerRequests) { + // Set some expectations for starting the client. + SetExpectationsForTiclStart(2); + + // Start the ticl. + StartClient(); + + // Make the server to client message. + ServerToClientMessage message; + InitServerHeader(client.get()->GetClientToken(), message.mutable_header()); + + // Add a registration sync request message. + message.mutable_registration_sync_request_message(); + + // Add an info request message. + message.mutable_info_request_message()->add_info_type( + InfoRequestMessage_InfoType_GET_PERFORMANCE_COUNTERS); + + // Give it to the prototol handler. + ProcessIncomingMessage(message, EndOfTestWaitTime()); + + // Make sure that the message is as expected. + ClientToServerMessage client_msg; + client_msg.ParseFromString(outgoing_messages[1]); + ASSERT_TRUE(client_msg.has_info_message()); + ASSERT_TRUE(client_msg.has_registration_sync_message()); +} + +// Tests that an incoming unknown failure message results in the app being +// informed about it. +TEST_F(InvalidationClientImplTest, IncomingErrorMessage) { + SetExpectationsForTiclStart(1); + + // Set up listener expectation for error. + EXPECT_CALL(listener, InformError(Eq(client.get()), _)); + + // Start the ticl. + StartClient(); + + // Give the error message to the protocol handler. + ServerToClientMessage message; + InitServerHeader(client.get()->GetClientToken(), message.mutable_header()); + InitErrorMessage(ErrorMessage_Code_UNKNOWN_FAILURE, "Some error message", + message.mutable_error_message()); + ProcessIncomingMessage(message, EndOfTestWaitTime()); +} + +// Tests that an incoming auth failure message results in the app being informed +// about it and the registrations being removed. +TEST_F(InvalidationClientImplTest, IncomingAuthErrorMessage) { + SetExpectationsForTiclStart(2); + + // One object to register for. + int num_objects = 1; + vector<ObjectIdP> oid_protos; + vector<ObjectId> oids; + InitTestObjectIds(num_objects, &oid_protos); + ConvertFromObjectIdProtos(oid_protos, &oids); + + // Expect error and registration failure from the ticl. + EXPECT_CALL(listener, InformError(Eq(client.get()), _)); + EXPECT_CALL(listener, InformRegistrationFailure(Eq(client.get()), Eq(oids[0]), + Eq(false), _)); + + // Start the client. + StartClient(); + + // Register and let the message be sent out. + client.get()->Register(oids[0]); + internal_scheduler->PassTime( + GetMaxBatchingDelay(config.protocol_handler_config())); + + // Give this message to the protocol handler. + ServerToClientMessage message; + InitServerHeader(client.get()->GetClientToken(), message.mutable_header()); + InitErrorMessage(ErrorMessage_Code_AUTH_FAILURE, "Auth error message", + message.mutable_error_message()); + ProcessIncomingMessage(message, EndOfTestWaitTime()); +} + +// Tests that a registration that times out results in a reg sync message being +// sent out. +TEST_F(InvalidationClientImplTest, NetworkTimeouts) { + // Set some expectations for starting the client. + SetExpectationsForTiclStart(3); + + // One object to register for. + int num_objects = 1; + vector<ObjectIdP> oid_protos; + vector<ObjectId> oids; + InitTestObjectIds(num_objects, &oid_protos); + ConvertFromObjectIdProtos(oid_protos, &oids); + + // Start the client. + StartClient(); + + // Register for an object. + client.get()->Register(oids[0]); + + // Let the registration message be sent out. + internal_scheduler->PassTime( + GetMaxBatchingDelay(config.protocol_handler_config())); + + // Now let the network timeout occur and an info message be sent. + TimeDelta timeout_delay = GetMaxDelay(config.network_timeout_delay_ms()); + internal_scheduler->PassTime(timeout_delay); + + // Check that the message sent out is an info message asking for the server's + // summary. + ClientToServerMessage client_msg2; + client_msg2.ParseFromString(outgoing_messages[2]); + ASSERT_TRUE(client_msg2.has_info_message()); + ASSERT_TRUE( + client_msg2.info_message().server_registration_summary_requested()); + internal_scheduler->PassTime(EndOfTestWaitTime()); +} + +// Tests that an incoming message without registration summary does not +// cause the registration summary in the client to be changed. +TEST_F(InvalidationClientImplTest, NoRegistrationSummary) { + // Test plan: Initialze the ticl, let it get a token with a ServerToClient + // message that has no registration summary. + + // Set some expectations for starting the client and start the client. + // Give it a summary with 1 reg. + reg_summary.get()->set_num_registrations(1); + SetExpectationsForTiclStart(1); + StartClient(); + + // Now give it an message with no summary. It should not reset to a summary + // with zero registrations. + reg_summary.reset(NULL); + ServerToClientMessage message; + InitServerHeader(client.get()->GetClientToken(), message.mutable_header()); + ProcessIncomingMessage(message, EndOfTestWaitTime()); + + // Check that the registration manager state did not change. + string manager_serial_state; + client->GetRegistrationManagerStateAsSerializedProto(&manager_serial_state); + RegistrationManagerStateP reg_manager_state; + reg_manager_state.ParseFromString(manager_serial_state); + + // Check that the registration manager state's number of registrations is 1. + TLOG(logger, INFO, "Reg manager state: %s", + ProtoHelpers::ToString(reg_manager_state).c_str()); + ASSERT_EQ(1, reg_manager_state.server_summary().num_registrations()); +} + +// Tests that heartbeats are sent out as time advances. +TEST_F(InvalidationClientImplTest, Heartbeats) { + // Set some expectations for starting the client. + SetExpectationsForTiclStart(2); + + // Start the client. + StartClient(); + + // Now let the heartbeat occur and an info message be sent. + TimeDelta heartbeat_delay = GetMaxDelay(config.heartbeat_interval_ms() + + config.protocol_handler_config().batching_delay_ms()); + internal_scheduler->PassTime(heartbeat_delay); + + // Check that the heartbeat is sent and it does not ask for the server's + // summary. + ClientToServerMessage client_msg1; + client_msg1.ParseFromString(outgoing_messages[1]); + ASSERT_TRUE(client_msg1.has_info_message()); + ASSERT_FALSE( + client_msg1.info_message().server_registration_summary_requested()); + internal_scheduler->PassTime(EndOfTestWaitTime()); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-util.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-util.h new file mode 100644 index 0000000..d5a2cc1 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-util.h @@ -0,0 +1,40 @@ +// Copyright 2012 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. + +// Useful utility functions for the TICL + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_UTIL_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_UTIL_H_ + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/time.h" + +namespace invalidation { + +class InvalidationClientUtil { + public: + /* Returns the time in milliseconds. */ + static int64 GetTimeInMillis(const Time& time) { + return time.ToInternalValue() / Time::kMicrosecondsPerMillisecond; + } + + /* Returns the current time in the scheduler's epoch in milliseconds. */ + static int64 GetCurrentTimeMs(Scheduler* scheduler) { + return GetTimeInMillis(scheduler->GetCurrentTime()); + } +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_INVALIDATION_CLIENT_UTIL_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/log-macro.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/log-macro.h new file mode 100644 index 0000000..1ac374a --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/log-macro.h @@ -0,0 +1,23 @@ +// Copyright 2012 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. + +// A simple logging macro specifically for the invalidation client library. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_LOG_MACRO_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_LOG_MACRO_H_ + +#define TLOG(logger, level, str, ...) \ + logger->Log(Logger::level ## _LEVEL, __FILE__, __LINE__, str, ##__VA_ARGS__); + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_LOG_MACRO_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/object-id-digest-utils.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/object-id-digest-utils.cc new file mode 100644 index 0000000..182e9ca --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/object-id-digest-utils.cc @@ -0,0 +1,38 @@ +// Copyright 2012 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. + +// Digest-related utilities for object ids. + +#include "google/cacheinvalidation/impl/object-id-digest-utils.h" + +namespace invalidation { + +string ObjectIdDigestUtils::GetDigest( + const ObjectIdP& object_id, DigestFunction* digest_fn) { + digest_fn->Reset(); + int source = object_id.source(); + string buffer(4, 0); + + // Little endian number for type followed by bytes. + buffer[0] = source & 0xff; + buffer[1] = (source >> 8) & 0xff; + buffer[2] = (source >> 16) & 0xff; + buffer[3] = (source >> 24) & 0xff; + + digest_fn->Update(buffer); + digest_fn->Update(object_id.name()); + return digest_fn->GetDigest(); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/object-id-digest-utils.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/object-id-digest-utils.h new file mode 100644 index 0000000..8adb316 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/object-id-digest-utils.h @@ -0,0 +1,49 @@ +// Copyright 2012 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. + +// Digest-related utilities for object ids. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_OBJECT_ID_DIGEST_UTILS_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_OBJECT_ID_DIGEST_UTILS_H_ + +#include <map> + +#include "google/cacheinvalidation/deps/digest-function.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::map; + +class ObjectIdDigestUtils { + public: + /* Returns the digest of the set of keys in the given map. */ + template<typename T> + static string GetDigest( + map<string, T> registrations, DigestFunction* digest_fn) { + digest_fn->Reset(); + for (map<string, ObjectIdP>::iterator iter = registrations.begin(); + iter != registrations.end(); ++iter) { + digest_fn->Update(iter->first); + } + return digest_fn->GetDigest(); + } + + /* Returns the digest of object_id using digest_fn. */ + static string GetDigest( + const ObjectIdP& object_id, DigestFunction* digest_fn); +}; +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_OBJECT_ID_DIGEST_UTILS_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/persistence-utils.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/persistence-utils.cc new file mode 100644 index 0000000..e9c1b3d --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/persistence-utils.cc @@ -0,0 +1,61 @@ +// Copyright 2012 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. + +// Utility methods for handling the Ticl persistent state. + +#include "google/cacheinvalidation/impl/persistence-utils.h" + +namespace invalidation { + +void PersistenceUtils::SerializeState( + PersistentTiclState state, DigestFunction* digest_fn, string* result) { + string mac = GenerateMac(state, digest_fn); + PersistentStateBlob blob; + blob.mutable_ticl_state()->CopyFrom(state); + blob.set_authentication_code(mac); + blob.SerializeToString(result); +} + +bool PersistenceUtils::DeserializeState( + Logger* logger, const string& state_blob_bytes, DigestFunction* digest_fn, + PersistentTiclState* ticl_state) { + PersistentStateBlob state_blob; + state_blob.ParseFromString(state_blob_bytes); + if (!state_blob.IsInitialized()) { + TLOG(logger, WARNING, "could not parse state blob from %s", + state_blob_bytes.c_str()); + return false; + } + + // Check the mac in the envelope against the recomputed mac from the state. + ticl_state->CopyFrom(state_blob.ticl_state()); + const string& mac = GenerateMac(*ticl_state, digest_fn); + if (mac != state_blob.authentication_code()) { + TLOG(logger, WARNING, "Ticl state failed MAC check: computed %s vs %s", + mac.c_str(), state_blob.authentication_code().c_str()); + return false; + } + return true; +} + +string PersistenceUtils::GenerateMac( + const PersistentTiclState& state, DigestFunction* digest_fn) { + string serialized; + state.SerializeToString(&serialized); + digest_fn->Reset(); + digest_fn->Update(serialized); + return digest_fn->GetDigest(); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/persistence-utils.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/persistence-utils.h new file mode 100644 index 0000000..e9dc96e --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/persistence-utils.h @@ -0,0 +1,54 @@ +// Copyright 2012 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. + +// Utility methods for handling the Ticl persistent state. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_PERSISTENCE_UTILS_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_PERSISTENCE_UTILS_H_ + +#include <string> + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/digest-function.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" +#include "google/cacheinvalidation/impl/log-macro.h" + +namespace invalidation { + +class PersistenceUtils { + public: + /* Serializes a Ticl state blob. */ + static void SerializeState( + PersistentTiclState state, DigestFunction* digest_fn, string* result); + + /* Deserializes a Ticl state blob. Returns whether the parsed state could be + * parsed. + */ + static bool DeserializeState( + Logger* logger, const string& state_blob_bytes, DigestFunction* digest_fn, + PersistentTiclState* ticl_state); + + /* Returns a message authentication code over state. */ + static string GenerateMac( + const PersistentTiclState& state, DigestFunction* digest_fn); + + private: + PersistenceUtils() { + // Prevent instantiation. + } +}; // class PersistenceUtils + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_PERSISTENCE_UTILS_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-converter.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-converter.cc new file mode 100644 index 0000000..e6d54b6 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-converter.cc @@ -0,0 +1,62 @@ +// Copyright 2012 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. + +// Utilities to convert between protobufs and externally-exposed types in the +// Ticl. + +#include "google/cacheinvalidation/impl/proto-converter.h" + +#include "google/cacheinvalidation/deps/logging.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" + +namespace invalidation { + +void ProtoConverter::ConvertFromObjectIdProto( + const ObjectIdP& object_id_proto, ObjectId* object_id) { + object_id->Init(object_id_proto.source(), object_id_proto.name()); +} + +void ProtoConverter::ConvertToObjectIdProto( + const ObjectId& object_id, ObjectIdP* object_id_proto) { + object_id_proto->set_source(object_id.source()); + object_id_proto->set_name(object_id.name()); +} + +void ProtoConverter::ConvertFromInvalidationProto( + const InvalidationP& invalidation_proto, Invalidation* invalidation) { + ObjectId object_id; + ConvertFromObjectIdProto(invalidation_proto.object_id(), &object_id); + bool is_trickle_restart = invalidation_proto.is_trickle_restart(); + if (invalidation_proto.has_payload()) { + invalidation->Init(object_id, invalidation_proto.version(), + invalidation_proto.payload(), is_trickle_restart); + } else { + invalidation->Init(object_id, invalidation_proto.version(), + is_trickle_restart); + } +} + +void ProtoConverter::ConvertToInvalidationProto( + const Invalidation& invalidation, InvalidationP* invalidation_proto) { + ConvertToObjectIdProto( + invalidation.object_id(), invalidation_proto->mutable_object_id()); + invalidation_proto->set_version(invalidation.version()); + if (invalidation.has_payload()) { + invalidation_proto->set_payload(invalidation.payload()); + } + bool is_trickle_restart = invalidation.is_trickle_restart_for_internal_use(); + invalidation_proto->set_is_trickle_restart(is_trickle_restart); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-converter.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-converter.h new file mode 100644 index 0000000..7c7c432 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-converter.h @@ -0,0 +1,65 @@ +// Copyright 2012 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. + +// Utilities to convert between protobufs and externally-exposed types in the +// Ticl. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_PROTO_CONVERTER_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_PROTO_CONVERTER_H_ + +#include "google/cacheinvalidation/include/types.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" + +namespace invalidation { + +class ProtoConverter { + public: + /* Converts an object id protocol buffer 'object_id_proto' to the + * corresponding external type 'object_id'. + */ + static void ConvertFromObjectIdProto( + const ObjectIdP& object_id_proto, ObjectId* object_id); + + /* Converts an object id 'object_id' to the corresponding protocol buffer + * 'object_id_proto'. + */ + static void ConvertToObjectIdProto( + const ObjectId& object_id, ObjectIdP* object_id_proto); + + /* Converts an invalidation protocol buffer 'invalidation_proto' to the + * corresponding external object 'invalidation'. + */ + static void ConvertFromInvalidationProto( + const InvalidationP& invalidation_proto, Invalidation* invalidation); + + /* Converts an invalidation to the corresponding protocol + * buffer and returns it. + */ + static void ConvertToInvalidationProto( + const Invalidation& invalidation, InvalidationP* invalidation_proto); + + static bool IsAllObjectIdP(const ObjectIdP& object_id_proto) { + return (object_id_proto.source() == ObjectSource_Type_INTERNAL) && + (object_id_proto.name() == ""); + } + + private: + ProtoConverter() { + // To prevent instantiation. + } +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_PROTO_CONVERTER_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-helpers.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-helpers.cc new file mode 100644 index 0000000..68cfe02 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-helpers.cc @@ -0,0 +1,473 @@ +// Copyright 2012 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. + +// Useful utility functions for the TICL + +#include "google/cacheinvalidation/impl/proto-helpers.h" + +#include <sstream> + +#include "google/cacheinvalidation/client_test_internal.pb.h" +#include "google/cacheinvalidation/deps/string_util.h" + +namespace invalidation { + +using ::ipc::invalidation::RegistrationManagerStateP; + +// Defines a ToString template method specialization for the given type. +#define DEFINE_TO_STRING(type) \ + template<> \ + string ProtoHelpers::ToString(const type& message) + +// Creates a stringstream |stream| and emits a leading "{ " to it. +#define BEGIN() \ + std::stringstream stream; \ + stream << "{ " + +// Emits a closing " }" on |stream| and returns the string that has been built. +#define END() \ + stream << " }"; \ + return stream.str() + +// Defines a trivial ToString method for a type (which just returns "<type>"). +#define DEFINE_TRIVIAL_TO_STRING(type) \ + DEFINE_TO_STRING(type) { \ + return "<" #type ">"; \ + } + +// Emits "field: <field value as string>" if |field| is present in |message|. +#define OPTIONAL(field) \ + if (message.has_##field()) { \ + stream << #field << ": " << ToString(message.field()) << " "; \ + } + +// Emits "field: <field value as string>" for each instance of field in message. +#define REPEATED(field) \ + for (int i = 0; i < message.field##_size(); ++i) { \ + stream << #field << ": " << ToString(message.field(i)) << " "; \ + } + +// Expands to a case branch that returns "name" if the implicitly tested +// expression is equal to the enum constant |name| in the given |type|. +#define ENUM_VALUE(type, name) case type##_##name: return #name + +// Expands to a default case branch that returns the string representation of +// |message|. +#define ENUM_UNKNOWN() default: return SimpleItoa(message) + +DEFINE_TO_STRING(bool) { + return message ? "true" : "false"; +} + +DEFINE_TO_STRING(int) { + std::stringstream stream; + stream << message; + return stream.str(); +} + +DEFINE_TO_STRING(int64) { + std::stringstream stream; + stream << message; + return stream.str(); +} + +/* + * Three arrays that store the representation of each character from 0 to 255. + * The ith number's octal representation is: CHAR_OCTAL_STRINGS1[i], + * CHAR_OCTAL_STRINGS2[i], CHAR_OCTAL_STRINGS3[i] + * <p> + * E.g., if the number 128, these arrays contain 2, 0, 0 at index 128. We use + * 3 char arrays instead of an array of strings since the code path for a + * character append operation is quite a bit shorterthan the append operation + * for strings. + */ +char ProtoHelpers::CHAR_OCTAL_STRINGS1[]; +char ProtoHelpers::CHAR_OCTAL_STRINGS2[]; +char ProtoHelpers::CHAR_OCTAL_STRINGS3[]; +bool ProtoHelpers::is_initialized = false; + +template<> +string ProtoHelpers::ToString(const string& bytes) { + // This is a racy initialization but that is ok since we are initializing to + // the same values. + if (!is_initialized) { + // Initialize the array with the Octal string values so that we do not have + // to do string.format for every byte during runtime. + for (int i = 0; i < ProtoHelpers::NUM_CHARS; i++) { + string value = StringPrintf("%03o", i); + ProtoHelpers::CHAR_OCTAL_STRINGS1[i] = value[0]; + ProtoHelpers::CHAR_OCTAL_STRINGS2[i] = value[1]; + ProtoHelpers::CHAR_OCTAL_STRINGS3[i] = value[2]; + } + is_initialized = true; + } + string builder; + builder.reserve(3 * bytes.length() + 2); + builder += "\""; + for (size_t i = 0; i < bytes.length(); i++) { + char c = bytes[i]; + switch (c) { + case '\n': builder += '\\'; builder += 'n'; break; + case '\r': builder += '\\'; builder += 'r'; break; + case '\t': builder += '\\'; builder += 't'; break; + case '\"': builder += '\\'; builder += '"'; break; + case '\\': builder += '\\'; builder += '\\'; break; + default: + if ((c >= 32) && (c < 127) && c != '\'') { + builder += c; + } else { + int byteValue = c; + if (c < 0) { + byteValue = c + 256; + } + builder += '\\'; + builder += CHAR_OCTAL_STRINGS1[byteValue]; + builder += CHAR_OCTAL_STRINGS2[byteValue]; + builder += CHAR_OCTAL_STRINGS3[byteValue]; + } + break; + } + } + builder += "\""; + return builder; +} + +void ProtoHelpers::InitRegistrationP(const ObjectIdP& oid, + RegistrationP::OpType op_type, RegistrationP* reg) { + reg->mutable_object_id()->CopyFrom(oid); + reg->set_op_type(op_type); +} + +void ProtoHelpers::InitRateLimitP(int window_ms, int count, + RateLimitP *rate_limit) { + rate_limit->set_window_ms(window_ms); + rate_limit->set_count(count); +} + +void ProtoHelpers::InitInitializeMessage( + const ApplicationClientIdP& application_client_id, const string& nonce, + InitializeMessage* init_msg) { + init_msg->set_client_type(application_client_id.client_type()); + init_msg->mutable_application_client_id()->CopyFrom( + application_client_id); + init_msg->set_nonce(nonce); + init_msg->set_digest_serialization_type( + InitializeMessage_DigestSerializationType_BYTE_BASED); +} + +void ProtoHelpers::InitClientVersion(const string& platform, + const string& application_info, ClientVersion* client_version) { + Version* version = client_version->mutable_version(); + version->set_major_version(Constants::kClientMajorVersion); + version->set_minor_version(Constants::kClientMinorVersion); + client_version->set_platform(platform); + client_version->set_language("C++"); + client_version->set_application_info(application_info); +} + +void ProtoHelpers::InitProtocolVersion(ProtocolVersion* protocol_version) { + Version* version = protocol_version->mutable_version(); + version->set_major_version(Constants::kProtocolMajorVersion); + version->set_minor_version(Constants::kProtocolMinorVersion); +} + +void ProtoHelpers::InitConfigVersion(Version* config_version) { + config_version->set_major_version(Constants::kConfigMajorVersion); + config_version->set_minor_version(Constants::kConfigMinorVersion); +} + +DEFINE_TO_STRING(ErrorMessage::Code) { + switch (message) { + ENUM_VALUE(ErrorMessage_Code, AUTH_FAILURE); + ENUM_VALUE(ErrorMessage_Code, UNKNOWN_FAILURE); + ENUM_UNKNOWN(); + } +} +DEFINE_TO_STRING(InfoRequestMessage::InfoType) { + switch (message) { + ENUM_VALUE(InfoRequestMessage_InfoType, GET_PERFORMANCE_COUNTERS); + ENUM_UNKNOWN(); + } +} + +DEFINE_TO_STRING(InitializeMessage::DigestSerializationType) { + switch (message) { + ENUM_VALUE(InitializeMessage_DigestSerializationType, BYTE_BASED); + ENUM_VALUE(InitializeMessage_DigestSerializationType, NUMBER_BASED); + ENUM_UNKNOWN(); + } +} + +DEFINE_TO_STRING(StatusP::Code) { + switch (message) { + ENUM_VALUE(StatusP_Code, SUCCESS); + ENUM_VALUE(StatusP_Code, TRANSIENT_FAILURE); + ENUM_VALUE(StatusP_Code, PERMANENT_FAILURE); + ENUM_UNKNOWN(); + } +} + +DEFINE_TO_STRING(RegistrationP::OpType) { + switch (message) { + ENUM_VALUE(RegistrationP_OpType, REGISTER); + ENUM_VALUE(RegistrationP_OpType, UNREGISTER); + ENUM_UNKNOWN(); + } +} + +DEFINE_TO_STRING(RegistrationSyncRequestMessage) { + BEGIN(); + END(); +} + +DEFINE_TO_STRING(Version) { + BEGIN(); + OPTIONAL(major_version); + OPTIONAL(minor_version); + END(); +} + +DEFINE_TO_STRING(ClientVersion) { + BEGIN(); + OPTIONAL(version); + OPTIONAL(platform); + OPTIONAL(language); + OPTIONAL(application_info); + END(); +} + +DEFINE_TO_STRING(ProtocolVersion) { + BEGIN(); + OPTIONAL(version); + END(); +} + +DEFINE_TO_STRING(InfoRequestMessage) { + BEGIN(); + REPEATED(info_type); + END(); +} + +DEFINE_TO_STRING(ConfigChangeMessage) { + BEGIN(); + OPTIONAL(next_message_delay_ms); + END(); +} + +DEFINE_TO_STRING(PropertyRecord) { + BEGIN(); + OPTIONAL(name); + OPTIONAL(value); + END(); +} + +DEFINE_TO_STRING(RateLimitP) { + BEGIN(); + OPTIONAL(window_ms); + OPTIONAL(count); + END(); +} + +DEFINE_TO_STRING(ProtocolHandlerConfigP) { + BEGIN(); + OPTIONAL(batching_delay_ms); + REPEATED(rate_limit); + END(); +} + +DEFINE_TO_STRING(ClientConfigP) { + BEGIN(); + OPTIONAL(version); + OPTIONAL(network_timeout_delay_ms); + OPTIONAL(write_retry_delay_ms); + OPTIONAL(heartbeat_interval_ms); + OPTIONAL(perf_counter_delay_ms); + OPTIONAL(max_exponential_backoff_factor); + OPTIONAL(smear_percent); + OPTIONAL(is_transient); + OPTIONAL(initial_persistent_heartbeat_delay_ms); + OPTIONAL(protocol_handler_config); + END(); +} + +DEFINE_TO_STRING(InfoMessage) { + BEGIN(); + OPTIONAL(client_version); + REPEATED(config_parameter); + REPEATED(performance_counter); + OPTIONAL(server_registration_summary_requested); + END(); +} + +DEFINE_TO_STRING(ErrorMessage) { + BEGIN(); + OPTIONAL(code); + OPTIONAL(description); + END(); +} + +DEFINE_TO_STRING(RegistrationSummary) { + BEGIN(); + OPTIONAL(num_registrations); + OPTIONAL(registration_digest); + END(); +} + +DEFINE_TO_STRING(ObjectIdP) { + BEGIN(); + OPTIONAL(source); + OPTIONAL(name); + END(); +} + +DEFINE_TO_STRING(InvalidationP) { + BEGIN(); + OPTIONAL(object_id); + OPTIONAL(is_known_version); + OPTIONAL(version); + OPTIONAL(is_trickle_restart); + OPTIONAL(payload); + END(); +} + +DEFINE_TO_STRING(AckHandleP) { + BEGIN(); + OPTIONAL(invalidation); + END(); +} + +DEFINE_TO_STRING(ApplicationClientIdP) { + BEGIN(); + OPTIONAL(client_type); + OPTIONAL(client_name); + END(); +} + +DEFINE_TO_STRING(StatusP) { + BEGIN(); + OPTIONAL(code); + OPTIONAL(description); + END(); +} + +DEFINE_TO_STRING(RegistrationP) { + BEGIN(); + OPTIONAL(object_id); + OPTIONAL(op_type); + END(); +} + +DEFINE_TO_STRING(RegistrationStatus) { + BEGIN(); + OPTIONAL(registration); + OPTIONAL(status); + END(); +} + +DEFINE_TO_STRING(ClientHeader) { + BEGIN(); + OPTIONAL(protocol_version); + OPTIONAL(client_token); + OPTIONAL(registration_summary); + OPTIONAL(client_time_ms); + OPTIONAL(max_known_server_time_ms); + OPTIONAL(message_id); + END(); +} + +DEFINE_TO_STRING(InitializeMessage) { + BEGIN(); + OPTIONAL(client_type); + OPTIONAL(nonce); + OPTIONAL(application_client_id); + OPTIONAL(digest_serialization_type); + END(); +} + +DEFINE_TO_STRING(RegistrationMessage) { + BEGIN(); + REPEATED(registration); + END(); +} + +DEFINE_TO_STRING(InvalidationMessage) { + BEGIN(); + REPEATED(invalidation); + END(); +} + +DEFINE_TO_STRING(RegistrationSubtree) { + BEGIN(); + REPEATED(registered_object); + END(); +} +DEFINE_TO_STRING(RegistrationSyncMessage) { + BEGIN(); + REPEATED(subtree); + END(); +} + +DEFINE_TO_STRING(ClientToServerMessage) { + BEGIN(); + OPTIONAL(header); + OPTIONAL(initialize_message); + OPTIONAL(registration_message); + OPTIONAL(registration_sync_message); + OPTIONAL(invalidation_ack_message); + OPTIONAL(info_message); + END(); +} + +DEFINE_TO_STRING(ServerHeader) { + BEGIN(); + OPTIONAL(protocol_version); + OPTIONAL(client_token); + OPTIONAL(registration_summary); + OPTIONAL(server_time_ms); + OPTIONAL(message_id); + END(); +} + +DEFINE_TO_STRING(TokenControlMessage) { + BEGIN(); + OPTIONAL(new_token); + END(); +} + +DEFINE_TO_STRING(RegistrationStatusMessage) { + BEGIN(); + REPEATED(registration_status); + END(); +} + +DEFINE_TO_STRING(ServerToClientMessage) { + BEGIN(); + OPTIONAL(header); + OPTIONAL(token_control_message); + OPTIONAL(invalidation_message); + OPTIONAL(registration_status_message); + OPTIONAL(registration_sync_request_message); + OPTIONAL(info_request_message); + END(); +} + +DEFINE_TO_STRING(RegistrationManagerStateP) { + BEGIN(); + OPTIONAL(client_summary); + OPTIONAL(server_summary); + REPEATED(registered_objects); + END(); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-helpers.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-helpers.h new file mode 100644 index 0000000..aa6023e --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-helpers.h @@ -0,0 +1,143 @@ +// Copyright 2012 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. + +// Helper utilities for dealing with protocol buffers. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_PROTO_HELPERS_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_PROTO_HELPERS_H_ + +#include <sstream> +#include <string> + +#include "google/cacheinvalidation/client_protocol.pb.h" +#include "google/cacheinvalidation/deps/logging.h" +#include "google/cacheinvalidation/deps/stl-namespace.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" +#include "google/cacheinvalidation/impl/constants.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::string; +using ::ipc::invalidation::ProtocolVersion; + +// Functor to compare various protocol messages. +struct ProtoCompareLess { + bool operator()(const ObjectIdP& object_id1, + const ObjectIdP& object_id2) const { + // If the sources differ, then the one with the smaller source is the + // smaller object id. + int source_diff = object_id1.source() - object_id2.source(); + if (source_diff != 0) { + return source_diff < 0; + } + // Otherwise, the one with the smaller name is the smaller object id. + return object_id1.name().compare(object_id2.name()) < 0; + } + + bool operator()(const InvalidationP& inv1, + const InvalidationP& inv2) const { + const ProtoCompareLess& compare_less_than = *this; + // If the object ids differ, then the one with the smaller object id is the + // smaller invalidation. + if (compare_less_than(inv1.object_id(), inv2.object_id())) { + return true; + } + if (compare_less_than(inv2.object_id(), inv1.object_id())) { + return false; + } + + // Otherwise, the object ids are the same, so we need to look at the + // versions. + + // We define an unknown version to be less than a known version. + int64 known_version_diff = + inv1.is_known_version() - inv2.is_known_version(); + if (known_version_diff != 0) { + return known_version_diff < 0; + } + + // Otherwise, they're both known both unknown, so the one with the smaller + // version is the smaller invalidation. + return inv1.version() < inv2.version(); + } + + bool operator()(const RegistrationSubtree& reg_subtree1, + const RegistrationSubtree& reg_subtree2) const { + const RepeatedPtrField<ObjectIdP>& objects1 = + reg_subtree1.registered_object(); + const RepeatedPtrField<ObjectIdP>& objects2 = + reg_subtree2.registered_object(); + // If they have different numbers of objects, the one with fewer is smaller. + if (objects1.size() != objects2.size()) { + return objects1.size() < objects2.size(); + } + // Otherwise, compare the object ids in order. + RepeatedPtrField<ObjectIdP>::const_iterator iter1, iter2; + const ProtoCompareLess& compare_less_than = *this; + for (iter1 = objects1.begin(), iter2 = objects2.begin(); + iter1 != objects1.end(); ++iter1, ++iter2) { + if (compare_less_than(*iter1, *iter2)) { + return true; + } + if (compare_less_than(*iter2, *iter1)) { + return false; + } + } + // The registration subtrees are the same. + return false; + } +}; + +// Other protocol message utilities. +class ProtoHelpers { + public: + // Converts a value to a printable/readable string format. + template<typename T> + static string ToString(const T& value); + + // Initializes |reg| to be a (un) registration for object |oid|. + static void InitRegistrationP(const ObjectIdP& oid, + RegistrationP::OpType op_type, RegistrationP* reg); + + static void InitInitializeMessage( + const ApplicationClientIdP& application_client_id, const string& nonce, + InitializeMessage* init_msg); + + // Initializes |protocol_version| to the current protocol version. + static void InitProtocolVersion(ProtocolVersion* protocol_version); + + // Initializes |client_version| to the current client version. + static void InitClientVersion(const string& platform, + const string& application_info, ClientVersion* client_version); + + // Initializes |config_version| to the current config version. + static void InitConfigVersion(Version* config_version); + + // Initializes |rate_limit| with the given window interval and count of + // messages. + static void InitRateLimitP(int window_ms, int count, RateLimitP *rate_limit); + + private: + static const int NUM_CHARS = 256; + static char CHAR_OCTAL_STRINGS1[NUM_CHARS]; + static char CHAR_OCTAL_STRINGS2[NUM_CHARS]; + static char CHAR_OCTAL_STRINGS3[NUM_CHARS]; + + // Have the above arrays been initialized or not. + static bool is_initialized; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_PROTO_HELPERS_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler.cc new file mode 100644 index 0000000..2f233b5 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler.cc @@ -0,0 +1,442 @@ +// Copyright 2012 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. + +// Client for interacting with low-level protocol messages. + +#include "google/cacheinvalidation/impl/protocol-handler.h" + +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/impl/constants.h" +#include "google/cacheinvalidation/impl/invalidation-client-core.h" +#include "google/cacheinvalidation/impl/log-macro.h" +#include "google/cacheinvalidation/impl/proto-helpers.h" +#include "google/cacheinvalidation/impl/recurring-task.h" + +namespace invalidation { + +using ::ipc::invalidation::ConfigChangeMessage; +using ::ipc::invalidation::InfoMessage; +using ::ipc::invalidation::InitializeMessage; +using ::ipc::invalidation::InitializeMessage_DigestSerializationType_BYTE_BASED; +using ::ipc::invalidation::InvalidationMessage; +using ::ipc::invalidation::PropertyRecord; +using ::ipc::invalidation::RegistrationMessage; +using ::ipc::invalidation::RegistrationSyncMessage; +using ::ipc::invalidation::ServerHeader; +using ::ipc::invalidation::ServerToClientMessage; +using ::ipc::invalidation::TokenControlMessage; + +string ServerMessageHeader::ToString() const { + return StringPrintf( + "Token: %s, Summary: %s", ProtoHelpers::ToString(*token_).c_str(), + ProtoHelpers::ToString(*registration_summary_).c_str()); +} + +void ParsedMessage::InitFrom(const ServerToClientMessage& raw_message) { + base_message = raw_message; // Does a deep copy. + + // For each field, assign it to the corresponding protobuf field if + // present, else NULL. + header.InitFrom(&base_message.header().client_token(), + base_message.header().has_registration_summary() ? + &base_message.header().registration_summary() : NULL); + + token_control_message = base_message.has_token_control_message() ? + &base_message.token_control_message() : NULL; + + invalidation_message = base_message.has_invalidation_message() ? + &base_message.invalidation_message() : NULL; + + registration_status_message = + base_message.has_registration_status_message() ? + &base_message.registration_status_message() : NULL; + + registration_sync_request_message = + base_message.has_registration_sync_request_message() ? + &base_message.registration_sync_request_message() : NULL; + + config_change_message = base_message.has_config_change_message() ? + &base_message.config_change_message() : NULL; + + info_request_message = base_message.has_info_request_message() ? + &base_message.info_request_message() : NULL; + + error_message = base_message.has_error_message() ? + &base_message.error_message() : NULL; +} + +ProtocolHandler::ProtocolHandler( + const ProtocolHandlerConfigP& config, SystemResources* resources, + Smearer* smearer, Statistics* statistics, int client_type, + const string& application_name, ProtocolListener* listener, + TiclMessageValidator* msg_validator) + : logger_(resources->logger()), + internal_scheduler_(resources->internal_scheduler()), + network_(resources->network()), + throttle_(config.rate_limit(), internal_scheduler_, + NewPermanentCallback(this, &ProtocolHandler::SendMessageToServer)), + listener_(listener), + msg_validator_(msg_validator), + message_id_(1), + last_known_server_time_ms_(0), + next_message_send_time_ms_(0), + statistics_(statistics), + batcher_(resources->logger(), statistics), + client_type_(client_type) { + // Initialize client version. + ProtoHelpers::InitClientVersion(resources->platform(), application_name, + &client_version_); +} + +void ProtocolHandler::InitConfig(ProtocolHandlerConfigP* config) { + // Add rate limits. + + // Allow at most 3 messages every 5 seconds. + int window_ms = 5 * 1000; + int num_messages_per_window = 3; + + ProtoHelpers::InitRateLimitP(window_ms, num_messages_per_window, + config->add_rate_limit()); +} + +void ProtocolHandler::InitConfigForTest(ProtocolHandlerConfigP* config) { + // No rate limits. + int small_batch_delay_for_test = 200; + config->set_batching_delay_ms(small_batch_delay_for_test); + + // At most one message per second. + ProtoHelpers::InitRateLimitP(1000, 1, config->add_rate_limit()); + // At most six messages per minute. + ProtoHelpers::InitRateLimitP(60 * 1000, 6, config->add_rate_limit()); +} + +bool ProtocolHandler::HandleIncomingMessage(const string& incoming_message, + ParsedMessage* parsed_message) { + ServerToClientMessage message; + message.ParseFromString(incoming_message); + if (!message.IsInitialized()) { + TLOG(logger_, WARNING, "Incoming message is unparseable: %s", + ProtoHelpers::ToString(incoming_message).c_str()); + return false; + } + + // Validate the message. If this passes, we can blindly assume valid messages + // from here on. + TLOG(logger_, FINE, "Incoming message: %s", + ProtoHelpers::ToString(message).c_str()); + + if (!msg_validator_->IsValid(message)) { + statistics_->RecordError( + Statistics::ClientErrorType_INCOMING_MESSAGE_FAILURE); + TLOG(logger_, SEVERE, "Received invalid message: %s", + ProtoHelpers::ToString(message).c_str()); + return false; + } + + // Check the version of the message. + const ServerHeader& message_header = message.header(); + if (message_header.protocol_version().version().major_version() != + Constants::kProtocolMajorVersion) { + statistics_->RecordError( + Statistics::ClientErrorType_PROTOCOL_VERSION_FAILURE); + TLOG(logger_, SEVERE, "Dropping message with incompatible version: %s", + ProtoHelpers::ToString(message).c_str()); + return false; + } + + // Check if it is a ConfigChangeMessage which indicates that messages should + // no longer be sent for a certain duration. Perform this check before the + // token is even checked. + if (message.has_config_change_message()) { + const ConfigChangeMessage& config_change_msg = + message.config_change_message(); + statistics_->RecordReceivedMessage( + Statistics::ReceivedMessageType_CONFIG_CHANGE); + if (config_change_msg.has_next_message_delay_ms()) { + // Validator has ensured that it is positive. + next_message_send_time_ms_ = GetCurrentTimeMs() + + config_change_msg.next_message_delay_ms(); + } + return false; // Ignore all other messages in the envelope. + } + + if (message_header.server_time_ms() > last_known_server_time_ms_) { + last_known_server_time_ms_ = message_header.server_time_ms(); + } + parsed_message->InitFrom(message); + return true; +} + +bool ProtocolHandler::CheckServerToken(const string& server_token) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + const string& client_token = listener_->GetClientToken(); + + // If we do not have a client token yet, there is nothing to compare. The + // message must have an initialize message and the upper layer will do the + // appropriate checks. Hence, we return true if client_token is empty. + if (client_token.empty()) { + // No token. Return true so that we'll attempt to deliver a token control + // message (if any) to the listener in handleIncomingMessage. + return true; + } + + if (client_token != server_token) { + // Bad token - reject whole message. However, our channel can send us + // messages intended for other clients belonging to the same user, so don't + // log too loudly. + TLOG(logger_, INFO, "Incoming message has bad token: %s, %s", + ProtoHelpers::ToString(client_token).c_str(), + ProtoHelpers::ToString(server_token).c_str()); + statistics_->RecordError(Statistics::ClientErrorType_TOKEN_MISMATCH); + return false; + } + return true; +} + +void ProtocolHandler::SendInitializeMessage( + const ApplicationClientIdP& application_client_id, + const string& nonce, + BatchingTask* batching_task, + const string& debug_string) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + if (application_client_id.client_type() != client_type_) { + // This condition is not fatal, but it probably represents a bug somewhere + // if it occurs. + TLOG(logger_, WARNING, "Client type in application id does not match " + "constructor-provided type: %s vs %s", + ProtoHelpers::ToString(application_client_id).c_str(), client_type_); + } + + // Simply store the message in pending_initialize_message_ and send it + // when the batching task runs. + InitializeMessage* message = new InitializeMessage(); + ProtoHelpers::InitInitializeMessage(application_client_id, nonce, message); + TLOG(logger_, INFO, "Batching initialize message for client: %s, %s", + debug_string.c_str(), + ProtoHelpers::ToString(*message).c_str()); + batcher_.SetInitializeMessage(message); + batching_task->EnsureScheduled(debug_string); +} + +void ProtocolHandler::SendInfoMessage( + const vector<pair<string, int> >& performance_counters, + ClientConfigP* client_config, + bool request_server_registration_summary, + BatchingTask* batching_task) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + // Simply store the message in pending_info_message_ and send it + // when the batching task runs. + InfoMessage* message = new InfoMessage(); + message->mutable_client_version()->CopyFrom(client_version_); + + // Add configuration parameters. + if (client_config != NULL) { + message->mutable_client_config()->CopyFrom(*client_config); + } + + // Add performance counters. + for (size_t i = 0; i < performance_counters.size(); ++i) { + PropertyRecord* counter = message->add_performance_counter(); + counter->set_name(performance_counters[i].first); + counter->set_value(performance_counters[i].second); + } + + // Indicate whether we want the server's registration summary sent back. + message->set_server_registration_summary_requested( + request_server_registration_summary); + + TLOG(logger_, INFO, "Batching info message for client: %s", + ProtoHelpers::ToString(*message).c_str()); + batcher_.SetInfoMessage(message); + batching_task->EnsureScheduled("Send-info"); +} + +void ProtocolHandler::SendRegistrations( + const vector<ObjectIdP>& object_ids, + RegistrationP::OpType reg_op_type, + BatchingTask* batching_task) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + for (size_t i = 0; i < object_ids.size(); ++i) { + batcher_.AddRegistration(object_ids[i], reg_op_type); + } + batching_task->EnsureScheduled("Send-registrations"); +} + +void ProtocolHandler::SendInvalidationAck(const InvalidationP& invalidation, + BatchingTask* batching_task) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + // We could summarize acks if there are suppressing invalidations - we don't + // since it is unlikely to be too beneficial here. + batcher_.AddAck(invalidation); + batching_task->EnsureScheduled("Send-ack"); +} + +void ProtocolHandler::SendRegistrationSyncSubtree( + const RegistrationSubtree& reg_subtree, + BatchingTask* batching_task) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + TLOG(logger_, INFO, "Adding subtree: %s", + ProtoHelpers::ToString(reg_subtree).c_str()); + batcher_.AddRegSubtree(reg_subtree); + batching_task->EnsureScheduled("Send-reg-sync"); +} + +void ProtocolHandler::SendMessageToServer() { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + + if (next_message_send_time_ms_ > GetCurrentTimeMs()) { + TLOG(logger_, WARNING, "In quiet period: not sending message to server: " + "%s > %s", + SimpleItoa(next_message_send_time_ms_).c_str(), + SimpleItoa(GetCurrentTimeMs()).c_str()); + return; + } + + const bool has_client_token(!listener_->GetClientToken().empty()); + ClientToServerMessage builder; + if (!batcher_.ToBuilder(&builder, has_client_token)) { + TLOG(logger_, WARNING, "Unable to build message"); + return; + } + ClientHeader* outgoing_header = builder.mutable_header(); + InitClientHeader(outgoing_header); + + // Validate the message and send it. + ++message_id_; + if (!msg_validator_->IsValid(builder)) { + TLOG(logger_, SEVERE, "Tried to send invalid message: %s", + ProtoHelpers::ToString(builder).c_str()); + statistics_->RecordError( + Statistics::ClientErrorType_OUTGOING_MESSAGE_FAILURE); + return; + } + + TLOG(logger_, FINE, "Sending message to server: %s", + ProtoHelpers::ToString(builder).c_str()); + statistics_->RecordSentMessage(Statistics::SentMessageType_TOTAL); + string serialized; + builder.SerializeToString(&serialized); + network_->SendMessage(serialized); + + // Record that the message was sent. We do this inline to match what the + // Java Ticl, which is constrained by Android requirements, does. + listener_->HandleMessageSent(); +} + +void ProtocolHandler::InitClientHeader(ClientHeader* builder) { + CHECK(internal_scheduler_->IsRunningOnThread()) << "Not on internal thread"; + ProtoHelpers::InitProtocolVersion(builder->mutable_protocol_version()); + builder->set_client_time_ms(GetCurrentTimeMs()); + builder->set_message_id(StringPrintf("%d", message_id_)); + builder->set_max_known_server_time_ms(last_known_server_time_ms_); + builder->set_client_type(client_type_); + listener_->GetRegistrationSummary(builder->mutable_registration_summary()); + const string& client_token = listener_->GetClientToken(); + if (!client_token.empty()) { + TLOG(logger_, FINE, "Sending token on client->server message: %s", + ProtoHelpers::ToString(client_token).c_str()); + builder->set_client_token(client_token); + } +} + +bool Batcher::ToBuilder(ClientToServerMessage* builder, bool has_client_token) { + // Check if an initialize message needs to be sent. + if (pending_initialize_message_.get() != NULL) { + statistics_->RecordSentMessage(Statistics::SentMessageType_INITIALIZE); + builder->mutable_initialize_message()->CopyFrom( + *pending_initialize_message_); + pending_initialize_message_.reset(); + } + + // Note: Even if an initialize message is being sent, we can send additional + // messages such as registration messages, etc to the server. But if there is + // no token and an initialize message is not being sent, we cannot send any + // other message. + + if (!has_client_token && !builder->has_initialize_message()) { + // Cannot send any message. + TLOG(logger_, WARNING, + "Cannot send message since no token and no initialize msg: %s", + ProtoHelpers::ToString(*builder).c_str()); + statistics_->RecordError(Statistics::ClientErrorType_TOKEN_MISSING_FAILURE); + return false; + } + + // Check for pending batched operations and add to message builder if needed. + + // Add reg, acks, reg subtrees - clear them after adding. + if (!pending_acked_invalidations_.empty()) { + InitAckMessage(builder->mutable_invalidation_ack_message()); + statistics_->RecordSentMessage( + Statistics::SentMessageType_INVALIDATION_ACK); + } + + // Check regs. + if (!pending_registrations_.empty()) { + InitRegistrationMessage(builder->mutable_registration_message()); + statistics_->RecordSentMessage(Statistics::SentMessageType_REGISTRATION); + } + + // Check reg substrees. + if (!pending_reg_subtrees_.empty()) { + RegistrationSyncMessage* sync_message = + builder->mutable_registration_sync_message(); + set<RegistrationSubtree, ProtoCompareLess>::const_iterator iter; + for (iter = pending_reg_subtrees_.begin(); + iter != pending_reg_subtrees_.end(); ++iter) { + sync_message->add_subtree()->CopyFrom(*iter); + } + pending_reg_subtrees_.clear(); + statistics_->RecordSentMessage( + Statistics::SentMessageType_REGISTRATION_SYNC); + } + + // Check info message. + if (pending_info_message_.get() != NULL) { + statistics_->RecordSentMessage(Statistics::SentMessageType_INFO); + builder->mutable_info_message()->CopyFrom(*pending_info_message_); + pending_info_message_.reset(); + } + return true; +} + +void Batcher::InitRegistrationMessage( + RegistrationMessage* reg_message) { + CHECK(!pending_registrations_.empty()); + + // Run through the pending_registrations map. + map<ObjectIdP, RegistrationP::OpType, ProtoCompareLess>::iterator iter; + for (iter = pending_registrations_.begin(); + iter != pending_registrations_.end(); ++iter) { + ProtoHelpers::InitRegistrationP(iter->first, iter->second, + reg_message->add_registration()); + } + pending_registrations_.clear(); +} + +void Batcher::InitAckMessage(InvalidationMessage* ack_message) { + CHECK(!pending_acked_invalidations_.empty()); + + // Run through pending_acked_invalidations_ set. + set<InvalidationP, ProtoCompareLess>::iterator iter; + for (iter = pending_acked_invalidations_.begin(); + iter != pending_acked_invalidations_.end(); iter++) { + ack_message->add_invalidation()->CopyFrom(*iter); + } + pending_acked_invalidations_.clear(); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler.h new file mode 100644 index 0000000..c9e00c4 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler.h @@ -0,0 +1,424 @@ +// Copyright 2012 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. + +// A layer for interacting with low-level protocol messages. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_PROTOCOL_HANDLER_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_PROTOCOL_HANDLER_H_ + +#include <map> +#include <set> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/scoped_ptr.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" +#include "google/cacheinvalidation/impl/invalidation-client-util.h" +#include "google/cacheinvalidation/impl/proto-helpers.h" +#include "google/cacheinvalidation/impl/recurring-task.h" +#include "google/cacheinvalidation/impl/statistics.h" +#include "google/cacheinvalidation/impl/smearer.h" +#include "google/cacheinvalidation/impl/throttle.h" +#include "google/cacheinvalidation/impl/ticl-message-validator.h" + +namespace invalidation { + +class ProtocolHandler; + +using INVALIDATION_STL_NAMESPACE::make_pair; +using INVALIDATION_STL_NAMESPACE::map; +using INVALIDATION_STL_NAMESPACE::pair; +using INVALIDATION_STL_NAMESPACE::set; +using INVALIDATION_STL_NAMESPACE::string; + +/* + * Representation of a message header for use in a server message. + */ +struct ServerMessageHeader { + public: + ServerMessageHeader() { + } + + /* Initializes an instance. Note that this call *does not* make copies of + * the pointed-to data. Instances are always allocated inside a ParsedMessage, + * and the containing ParsedMessage owns the data. + * + * Arguments: + * init_token - server-sent token. + * init_registration_summary - summary over server registration state. + * If num_registations is not set, means no registration summary was + * received from the server. + */ + void InitFrom(const string* init_token, + const RegistrationSummary* init_registration_summary) { + token_ = init_token; + registration_summary_ = init_registration_summary; + } + + const string& token() const { + return *token_; + } + + // Returns the registration summary if any. + const RegistrationSummary* registration_summary() const { + return registration_summary_; + } + + // Returns a human-readable representation of this object for debugging. + string ToString() const; + + private: + // Server-sent token. + const string* token_; + + // Summary of the client's registration state at the server. + const RegistrationSummary* registration_summary_; + + DISALLOW_COPY_AND_ASSIGN(ServerMessageHeader); +}; + +/* + * Representation of a message receiver for the server. Such a message is + * guaranteed to be valid (i.e. checked by the message validator), but + * the session token is NOT checked. + */ +struct ParsedMessage { + public: + ParsedMessage() { + } + + ServerMessageHeader header; + + /* + * Each of these fields points to a field in the base_message + * ServerToClientMessage protobuf. It is non-null iff the corresponding hasYYY + * method in the protobuf would return true. + */ + const TokenControlMessage* token_control_message; + const InvalidationMessage* invalidation_message; + const RegistrationStatusMessage* registration_status_message; + const RegistrationSyncRequestMessage* registration_sync_request_message; + const ConfigChangeMessage* config_change_message; + const InfoRequestMessage* info_request_message; + const ErrorMessage* error_message; + + /* + * Initializes an instance from a |raw_message|. This function makes a copy of + * the message internally. + */ + void InitFrom(const ServerToClientMessage& raw_message); + + private: + ServerToClientMessage base_message; + DISALLOW_COPY_AND_ASSIGN(ParsedMessage); +}; + +/* + * Class that batches messages to be sent to the data center. + */ +class Batcher { + public: + Batcher(Logger* logger, Statistics* statistics) + : logger_(logger), statistics_(statistics) {} + + /* Sets the initialize |message| to be sent to the server. */ + void SetInitializeMessage(const InitializeMessage* message) { + pending_initialize_message_.reset(message); + } + + /* Sets the info |message| to be sent to the server. */ + void SetInfoMessage(const InfoMessage* message) { + pending_info_message_.reset(message); + } + + /* Adds a registration on |object_id| to be sent to the server. */ + void AddRegistration(const ObjectIdP& object_id, + const RegistrationP::OpType& reg_op_type) { + pending_registrations_[object_id] = reg_op_type; + } + + /* Adds an acknowledgment of |invalidation| to be sent to the server. */ + void AddAck(const InvalidationP& invalidation) { + pending_acked_invalidations_.insert(invalidation); + } + + /* Adds a registration subtree |reg_subtree| to be sent to the server. */ + void AddRegSubtree(const RegistrationSubtree& reg_subtree) { + pending_reg_subtrees_.insert(reg_subtree); + } + + /* + * Builds a message from the batcher state and resets the batcher. Returns + * whether the message could be built. + * + * Note that the returned message does NOT include a header. + */ + bool ToBuilder(ClientToServerMessage* builder, + bool has_client_token); + + /* + * Initializes a registration message based on registrations from + * |pending_registrations|. + * + * REQUIRES: pending_registrations.size() > 0 + */ + void InitRegistrationMessage(RegistrationMessage* reg_message); + + /* Initializes an invalidation ack message based on acks from + * |pending_acked_invalidations|. + * <p> + * REQUIRES: pending_acked_invalidations.size() > 0 + */ + void InitAckMessage(InvalidationMessage* ack_message); + + private: + Logger* const logger_; + + Statistics* const statistics_; + + /* Set of pending registrations stored as a map for overriding later + * operations. + */ + map<ObjectIdP, RegistrationP::OpType, ProtoCompareLess> + pending_registrations_; + + /* Set of pending invalidation acks. */ + set<InvalidationP, ProtoCompareLess> pending_acked_invalidations_; + + /* Set of pending registration sub trees for registration sync. */ + set<RegistrationSubtree, ProtoCompareLess> pending_reg_subtrees_; + + /* Pending initialization message to send to the server, if any. */ + scoped_ptr<const InitializeMessage> pending_initialize_message_; + + /* Pending info message to send to the server, if any. */ + scoped_ptr<const InfoMessage> pending_info_message_; + + DISALLOW_COPY_AND_ASSIGN(Batcher); +}; + +/* Listener for protocol events. The protocol client calls these methods when + * a message is received from the server. It guarantees that the call will be + * made on the internal thread that the SystemResources provides. When the + * protocol listener is called, the token has been checked and message + * validation has been completed (using the {@link TiclMessageValidator2}). + * That is, all of the methods below can assume that the nonce is null and the + * server token is valid. + */ +class ProtocolListener { + public: + ProtocolListener() {} + virtual ~ProtocolListener() {} + + /* Records that a message was sent to the server at the current time. */ + virtual void HandleMessageSent() = 0; + + /* Handles a change in network connectivity. */ + virtual void HandleNetworkStatusChange(bool is_online) = 0; + + /* Stores a summary of the current desired registrations. */ + virtual void GetRegistrationSummary(RegistrationSummary* summary) = 0; + + /* Returns the current server-assigned client token, if any. */ + virtual string GetClientToken() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ProtocolListener); +}; + +// Forward-declare the BatchingTask so that send* methods can take it. +class BatchingTask; + +/* Parses messages from the server and calls appropriate functions on the + * ProtocolListener to handle various types of message content. Also buffers + * message data from the client and constructs and sends messages to the server. + */ +class ProtocolHandler { + public: + /* Creates an instance. + * + * config - configuration for the client + * resources - resources to use + * smearer - a smearer to randomize delays + * statistics - track information about messages sent/received, etc + * client_type - client typecode + * application_name - name of the application using the library (for + * debugging/monitoring) + * listener - callback for protocol events + * msg_validator - validator for protocol messages + * Caller continues to own space for smearer. + */ + ProtocolHandler(const ProtocolHandlerConfigP& config, + SystemResources* resources, + Smearer* smearer, Statistics* statistics, + int client_type, const string& application_name, + ProtocolListener* listener, + TiclMessageValidator* msg_validator); + + /* Initializes |config| with default protocol handler config parameters. */ + static void InitConfig(ProtocolHandlerConfigP* config); + + /* Initializes |config| with protocol handler config parameters for unit + * tests. + */ + static void InitConfigForTest(ProtocolHandlerConfigP* config); + + /* Returns the next time a message is allowed to be sent to the server. + * Typically, this will be in the past, meaning that the client is free to + * send a message at any time. + */ + int64 GetNextMessageSendTimeMsForTest() { + return next_message_send_time_ms_; + } + + /* Sends a message to the server to request a client token. + * + * Arguments: + * client_type - client type code as assigned by the notification system's + * backend + * application_client_id - application-specific client id + * nonce - nonce for the request + * batching_task - recurring task to trigger batching. No ownership taken. + * debug_string - information to identify the caller + */ + void SendInitializeMessage( + const ApplicationClientIdP& application_client_id, + const string& nonce, + BatchingTask* batching_task, + const string& debug_string); + + /* Sends an info message to the server with the performance counters supplied + * in performance_counters and the config supplies in client_config (which + * could be null). + */ + void SendInfoMessage(const vector<pair<string, int> >& performance_counters, + ClientConfigP* client_config, + bool request_server_registration_summary, + BatchingTask* batching_task); + + /* Sends a registration request to the server. + * + * Arguments: + * object_ids - object ids on which to (un)register + * reg_op_type - whether to register or unregister + * batching_task - recurring task to trigger batching. No ownership taken. + */ + void SendRegistrations(const vector<ObjectIdP>& object_ids, + RegistrationP::OpType reg_op_type, + BatchingTask* batching_task); + + /* Sends an acknowledgement for invalidation to the server. */ + void SendInvalidationAck(const InvalidationP& invalidation, + BatchingTask* batching_task); + + /* Sends a single registration subtree to the server. + * + * Arguments: + * reg_subtree - subtree to send + * batching_task - recurring task to trigger batching. No ownership taken. + */ + void SendRegistrationSyncSubtree(const RegistrationSubtree& reg_subtree, + BatchingTask* batching_task); + + /* Sends pending data to the server (e.g., registrations, acks, registration + * sync messages). + * + * REQUIRES: caller do no further work after the method returns. + */ + void SendMessageToServer(); + + /* + * Handles a message from the server. If the message can be processed (i.e., + * is valid, is of the right version, and is not a silence message), returns + * a ParsedMessage representing it. Otherwise, returns NULL. + * + * This class intercepts and processes silence messages. In this case, it will + * discard any other data in the message. + * + * Note that this method does not check the session token of any message. + */ + bool HandleIncomingMessage(const string& incoming_message, + ParsedMessage* parsed_message); + + private: + /* Verifies that server_token matches the token currently held by the client. + */ + bool CheckServerToken(const string& server_token); + + /* Stores the header to include on a message to the server. */ + void InitClientHeader(ClientHeader* header); + + // Returns the current time in milliseconds. + int64 GetCurrentTimeMs() { + return InvalidationClientUtil::GetCurrentTimeMs(internal_scheduler_); + } + + friend class BatchingTask; + + // Information about the client, e.g., application name, OS, etc. + + ClientVersion client_version_; + + // A logger. + Logger* logger_; + + // Scheduler for the client's internal processing. + Scheduler* internal_scheduler_; + + // Network channel for sending and receiving messages to and from the server. + NetworkChannel* network_; + + // A throttler to prevent the client from sending too many messages in a given + // interval. + Throttle throttle_; + + // The protocol listener. + ProtocolListener* listener_; + + // Checks that messages (inbound and outbound) conform to basic validity + // constraints. + TiclMessageValidator* msg_validator_; + + /* A debug message id that is added to every message to the server. */ + int message_id_; + + // State specific to a client. If we want to support multiple clients, this + // could be in a map or could be eliminated (e.g., no batching). + + /* The last known time from the server. */ + int64 last_known_server_time_ms_; + + /* The next time before which a message cannot be sent to the server. If + * this is less than current time, a message can be sent at any time. + */ + int64 next_message_send_time_ms_; + + /* Statistics objects to track number of sent messages, etc. */ + Statistics* statistics_; + + // Batches messages to be sent to the server. + Batcher batcher_; + + // Type code for the client. + int client_type_; + + DISALLOW_COPY_AND_ASSIGN(ProtocolHandler); +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_PROTOCOL_HANDLER_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler_test.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler_test.cc new file mode 100644 index 0000000..1ca1568 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler_test.cc @@ -0,0 +1,674 @@ +// Copyright 2012 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. + +// Unit tests for the ProtocolHandler class. + +#include "google/cacheinvalidation/types.pb.h" +#include "google/cacheinvalidation/include/types.h" +#include "google/cacheinvalidation/deps/gmock.h" +#include "google/cacheinvalidation/deps/googletest.h" +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/impl/basic-system-resources.h" +#include "google/cacheinvalidation/impl/constants.h" +#include "google/cacheinvalidation/impl/invalidation-client-impl.h" +#include "google/cacheinvalidation/impl/protocol-handler.h" +#include "google/cacheinvalidation/impl/statistics.h" +#include "google/cacheinvalidation/impl/throttle.h" +#include "google/cacheinvalidation/impl/ticl-message-validator.h" +#include "google/cacheinvalidation/test/deterministic-scheduler.h" +#include "google/cacheinvalidation/test/test-logger.h" +#include "google/cacheinvalidation/test/test-utils.h" + +namespace invalidation { + +using ::ipc::invalidation::ClientType_Type_TEST; +using ::ipc::invalidation::ObjectSource_Type_TEST; +using ::testing::_; +using ::testing::AllOf; +using ::testing::ByRef; +using ::testing::DoAll; +using ::testing::ElementsAre; +using ::testing::EqualsProto; +using ::testing::Eq; +using ::testing::Matcher; +using ::testing::Property; +using ::testing::Return; +using ::testing::ReturnPointee; +using ::testing::SaveArg; +using ::testing::SetArgPointee; +using ::testing::StrictMock; +using ::testing::proto::WhenDeserializedAs; + +/* Returns whether two headers are equal. */ +bool HeaderEqual(const ServerMessageHeader& expected, + const ServerMessageHeader& actual) { + // If the token is different or if one of the registration summaries is NULL + // and the other is non-NULL, return false. + if (((expected.registration_summary() != NULL) != + (actual.registration_summary() != NULL)) || + (expected.token() != actual.token())) { + return false; + } + + // The tokens are the same and registration summaries are either both + // null or non-null. + return (expected.registration_summary() == NULL) || + ((expected.registration_summary()->num_registrations() == + actual.registration_summary()->num_registrations()) && + (expected.registration_summary()->registration_digest() == + actual.registration_summary()->registration_digest())); +} + +// A mock of the ProtocolListener interface. +class MockProtocolListener : public ProtocolListener { + public: + MOCK_METHOD0(HandleMessageSent, void()); + + MOCK_METHOD1(HandleNetworkStatusChange, void(bool)); // NOLINT + + MOCK_METHOD1(GetRegistrationSummary, void(RegistrationSummary*)); // NOLINT + + MOCK_METHOD0(GetClientToken, string()); +}; + +// Tests the basic functionality of the protocol handler. +class ProtocolHandlerTest : public UnitTestBase { + public: + virtual ~ProtocolHandlerTest() {} + + // Performs setup for protocol handler unit tests, e.g. creating resource + // components and setting up common expectations for certain mock objects. + virtual void SetUp() { + // Use a strict mock scheduler for the listener, since it shouldn't be used + // at all by the protocol handler. + UnitTestBase::SetUp(); + InitListenerExpectations(); + validator.reset(new TiclMessageValidator(logger)); // Create msg validator + + // Create the protocol handler object. + random.reset(new Random(InvalidationClientUtil::GetCurrentTimeMs( + resources.get()->internal_scheduler()))); + smearer.reset(new Smearer(random.get(), kDefaultSmearPercent)); + protocol_handler.reset( + new ProtocolHandler( + config, resources.get(), smearer.get(), statistics.get(), + ClientType_Type_TEST, "unit-test", &listener, validator.get())); + batching_task.reset( + new BatchingTask(protocol_handler.get(), smearer.get(), + TimeDelta::FromMilliseconds(config.batching_delay_ms()))); + } + + // Configuration for the protocol handler (uses defaults). + ProtocolHandlerConfigP config; + + // The protocol handler being tested. Created fresh for each test function. + scoped_ptr<ProtocolHandler> protocol_handler; + + // A mock protocol listener. We make this strict in order to have tight + // control over the interactions between this and the protocol handler. + // SetUp() installs expectations to allow GetClientToken() and + // GetRegistrationSummary() to be called any time and to give them + // reasonable behavior. + StrictMock<MockProtocolListener> listener; + + // Ticl message validator. We do not mock this, since the correctness of the + // protocol handler depends on it. + scoped_ptr<TiclMessageValidator> validator; + + // Token and registration summary for the mock listener to return when + // the protocol handler requests them. + string token; + RegistrationSummary summary; + + // A smearer to randomize delays. + scoped_ptr<Smearer> smearer; + + // A random number generator. + scoped_ptr<Random> random; + + // Batching task for the protocol handler. + scoped_ptr<BatchingTask> batching_task; + + void AddExpectationForHandleMessageSent() { + EXPECT_CALL(listener, HandleMessageSent()); + } + + /* + * Processes a |message| using the protocol handler, initializing + * |parsed_message| with the result. + * + * Returns whether the message could be parsed. + */ + bool ProcessMessage(ServerToClientMessage message, + ParsedMessage* parsed_message) { + string serialized; + message.SerializeToString(&serialized); + bool accepted = protocol_handler->HandleIncomingMessage( + serialized, parsed_message); + return accepted; + } + + private: + void InitListenerExpectations() { + // When the handler asks the listener for the client token, return whatever + // |token| currently is. + EXPECT_CALL(listener, GetClientToken()) + .WillRepeatedly(ReturnPointee(&token)); + + // If the handler asks the listener for a registration summary, respond by + // supplying a fake summary. + InitZeroRegistrationSummary(&summary); + EXPECT_CALL(listener, GetRegistrationSummary(_)) + .WillRepeatedly(SetArgPointee<0>(summary)); + } +}; + +// Asks the protocol handler to send an initialize message. Waits for the +// batching delay to pass. Checks that appropriate calls are made on the +// listener and that a proper message is sent on the network. +TEST_F(ProtocolHandlerTest, SendInitializeOnly) { + ApplicationClientIdP app_client_id; + app_client_id.set_client_name("unit-test-client-id"); + app_client_id.set_client_type(ClientType_Type_TEST); + + // Client's token is initially empty. Give it an arbitrary nonce. + token = ""; + string nonce = "unit-test-nonce"; + + // SendInitializeMessage checks that it's running on the work queue thread, so + // we need to schedule the call. + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), &ProtocolHandler::SendInitializeMessage, + app_client_id, nonce, batching_task.get(), "Startup")); + + AddExpectationForHandleMessageSent(); + ClientToServerMessage expected_message; + + // Build the header. + ClientHeader* header = expected_message.mutable_header(); + ProtoHelpers::InitProtocolVersion(header->mutable_protocol_version()); + header->mutable_registration_summary()->CopyFrom(summary); + header->set_max_known_server_time_ms(0); + header->set_message_id("1"); + + // Note: because the batching task is smeared, we don't know what the client's + // timestamp will be. We omit it from this proto and do a partial match in + // the EXPECT_CALL but also save the proto and check later that it doesn't + // contain anything we don't expect. + + // Create the expected initialize message. + InitializeMessage* initialize_message = + expected_message.mutable_initialize_message(); + initialize_message->set_client_type(ClientType_Type_TEST); + initialize_message->set_nonce(nonce); + initialize_message->mutable_application_client_id()->CopyFrom(app_client_id); + initialize_message->set_digest_serialization_type( + InitializeMessage_DigestSerializationType_BYTE_BASED); + + string actual_serialized; + EXPECT_CALL( + *network, + SendMessage(WhenDeserializedAs<ClientToServerMessage>( + // Check that the deserialized message has the initialize message and + // header fields we expect. + AllOf(Property(&ClientToServerMessage::initialize_message, + EqualsProto(*initialize_message)), + Property(&ClientToServerMessage::header, + ClientHeaderMatches(header)))))) + .WillOnce(SaveArg<0>(&actual_serialized)); + + // The actual message won't be sent until after the batching delay, which is + // smeared, so double it to be sure enough time will have passed. + TimeDelta wait_time = GetMaxBatchingDelay(config); + internal_scheduler->PassTime(wait_time); + + // By now we expect the message to have been sent, so we'll deserialize it + // and check that it doesn't have anything we don't expect. + ClientToServerMessage actual_message; + actual_message.ParseFromString(actual_serialized); + ASSERT_FALSE(actual_message.has_info_message()); + ASSERT_FALSE(actual_message.has_invalidation_ack_message()); + ASSERT_FALSE(actual_message.has_registration_message()); + ASSERT_FALSE(actual_message.has_registration_sync_message()); + ASSERT_GE(actual_message.header().client_time_ms(), + InvalidationClientUtil::GetTimeInMillis(start_time)); + ASSERT_LE(actual_message.header().client_time_ms(), + InvalidationClientUtil::GetTimeInMillis(start_time + wait_time)); +} + +// Tests the receipt of a token control message like what we'd expect in +// response to an initialize message. Check that appropriate calls are made on +// the protocol listener. +TEST_F(ProtocolHandlerTest, ReceiveTokenControlOnly) { + ServerToClientMessage message; + ServerHeader* header = message.mutable_header(); + string nonce = "fake nonce"; + InitServerHeader(nonce, header); + + string new_token = "new token"; + message.mutable_token_control_message()->set_new_token(new_token); + + ServerMessageHeader expected_header; + expected_header.InitFrom(&nonce, &header->registration_summary()); + ParsedMessage parsed_message; + ProcessMessage(message, &parsed_message); + ASSERT_TRUE(HeaderEqual(expected_header, parsed_message.header)); + ASSERT_TRUE(parsed_message.token_control_message != NULL); +} + +// Test that the protocol handler correctly buffers multiple message types. +// Tell it to send registrations, then unregistrations (with some overlap in the +// sets of objects). Then send some invalidation acks and finally a +// registration subtree. Wait for the batching interval to pass, and then check +// that the message sent out contains everything we expect. +TEST_F(ProtocolHandlerTest, SendMultipleMessageTypes) { + // Concoct some performance counters and config parameters, and ask to send + // an info message with them. + vector<pair<string, int> > perf_counters; + perf_counters.push_back(make_pair("x", 3)); + perf_counters.push_back(make_pair("y", 81)); + ClientConfigP client_config; + InvalidationClientImpl::InitConfig(&client_config); + + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), &ProtocolHandler::SendInfoMessage, + perf_counters, &client_config, true, batching_task.get())); + + // Synthesize a few test object ids. + vector<ObjectIdP> oids; + InitTestObjectIds(3, &oids); + + // Register for the first two. + vector<ObjectIdP> oid_vec; + oid_vec.push_back(oids[0]); + oid_vec.push_back(oids[1]); + + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), &ProtocolHandler::SendRegistrations, + oid_vec, RegistrationP_OpType_REGISTER, batching_task.get())); + + // Then unregister for the second and third. This overrides the registration + // on oids[1]. + oid_vec.clear(); + oid_vec.push_back(oids[1]); + oid_vec.push_back(oids[2]); + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), &ProtocolHandler::SendRegistrations, + oid_vec, RegistrationP_OpType_UNREGISTER, batching_task.get())); + + // Send a couple of invalidations. + vector<InvalidationP> invalidations; + MakeInvalidationsFromObjectIds(oids, &invalidations); + invalidations.pop_back(); + for (size_t i = 0; i < invalidations.size(); ++i) { + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), &ProtocolHandler::SendInvalidationAck, + invalidations[i], batching_task.get())); + } + + // Send a simple registration subtree. + RegistrationSubtree subtree; + subtree.add_registered_object()->CopyFrom(oids[0]); + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), &ProtocolHandler::SendRegistrationSyncSubtree, + subtree, batching_task.get())); + + AddExpectationForHandleMessageSent(); + + token = "test token"; + + // The message it sends should contain all of the expected information: + ClientToServerMessage expected_message; + + // Header. + ClientHeader* header = expected_message.mutable_header(); + ProtoHelpers::InitProtocolVersion(header->mutable_protocol_version()); + header->mutable_registration_summary()->CopyFrom(summary); + header->set_client_token(token); + header->set_max_known_server_time_ms(0); + header->set_message_id("1"); + + // Note: because the batching task is smeared, we don't know what the client's + // timestamp will be. We omit it from this proto and do a partial match in + // the EXPECT_CALL but also save the proto and check later that it doesn't + // contain anything we don't expect. + + // Registrations. + RegistrationMessage* reg_message = + expected_message.mutable_registration_message(); + RegistrationP* registration; + registration = reg_message->add_registration(); + registration->mutable_object_id()->CopyFrom(oids[0]); + registration->set_op_type(RegistrationP_OpType_REGISTER); + + registration = reg_message->add_registration(); + registration->mutable_object_id()->CopyFrom(oids[1]); + registration->set_op_type(RegistrationP_OpType_UNREGISTER); + + registration = reg_message->add_registration(); + registration->mutable_object_id()->CopyFrom(oids[2]); + registration->set_op_type(RegistrationP_OpType_UNREGISTER); + + // Registration sync message. + expected_message.mutable_registration_sync_message()->add_subtree() + ->CopyFrom(subtree); + + // Invalidation acks. + InvalidationMessage* invalidation_message = + expected_message.mutable_invalidation_ack_message(); + InitInvalidationMessage(invalidations, invalidation_message); + + // Info message. + InfoMessage* info_message = expected_message.mutable_info_message(); + ProtoHelpers::InitClientVersion("unit-test", "unit-test", + info_message->mutable_client_version()); + info_message->set_server_registration_summary_requested(true); + info_message->mutable_client_config()->CopyFrom(client_config); + PropertyRecord* prop_rec; + for (uint32 i = 0; i < perf_counters.size(); ++i) { + prop_rec = info_message->add_performance_counter(); + prop_rec->set_name(perf_counters[i].first); + prop_rec->set_value(perf_counters[i].second); + } + + string actual_serialized; + EXPECT_CALL( + *network, + SendMessage( + WhenDeserializedAs<ClientToServerMessage>( + // Check that the deserialized message has the invalidation acks, + // registrations, info message, and header fields we expect. + AllOf(Property(&ClientToServerMessage::invalidation_ack_message, + EqualsProto(*invalidation_message)), + Property(&ClientToServerMessage::registration_message, + EqualsProto(*reg_message)), + Property(&ClientToServerMessage::info_message, + EqualsProto(*info_message)), + Property(&ClientToServerMessage::header, + ClientHeaderMatches(header)))))) + .WillOnce(SaveArg<0>(&actual_serialized)); + + TimeDelta wait_time = GetMaxBatchingDelay(config); + internal_scheduler->PassTime(wait_time); + + ClientToServerMessage actual_message; + actual_message.ParseFromString(actual_serialized); + + ASSERT_FALSE(actual_message.has_initialize_message()); + ASSERT_GE(actual_message.header().client_time_ms(), + InvalidationClientUtil::GetTimeInMillis(start_time)); + ASSERT_LE(actual_message.header().client_time_ms(), + InvalidationClientUtil::GetTimeInMillis(start_time + wait_time)); +} + +// Check that if the protocol handler receives a message with several sub- +// messages set, it makes all the appropriate calls on the listener. +TEST_F(ProtocolHandlerTest, IncomingCompositeMessage) { + // Build up a message with a number of sub-messages in it: + ServerToClientMessage message; + + // First the header. + token = "test token"; + InitServerHeader(token, message.mutable_header()); + + // Fabricate a few object ids for use in invalidations and registration + // statuses. + vector<ObjectIdP> object_ids; + InitTestObjectIds(3, &object_ids); + + // Add invalidations. + vector<InvalidationP> invalidations; + MakeInvalidationsFromObjectIds(object_ids, &invalidations); + for (int i = 0; i < 3; ++i) { + message.mutable_invalidation_message()->add_invalidation()->CopyFrom( + invalidations[i]); + } + + // Add registration statuses. + vector<RegistrationStatus> registration_statuses; + MakeRegistrationStatusesFromObjectIds(object_ids, true, true, + ®istration_statuses); + for (int i = 0; i < 3; ++i) { + message.mutable_registration_status_message() + ->add_registration_status()->CopyFrom(registration_statuses[i]); + } + + // Add a registration sync request message. + message.mutable_registration_sync_request_message(); + + // Add an info request message. + message.mutable_info_request_message()->add_info_type( + InfoRequestMessage_InfoType_GET_PERFORMANCE_COUNTERS); + + // The header we expect the listener to be called with. + ServerMessageHeader expected_header; + expected_header.InitFrom(&token, &summary); + + ParsedMessage parsed_message; + ProcessMessage(message, &parsed_message); + ASSERT_TRUE(HeaderEqual(expected_header, parsed_message.header)); + ASSERT_TRUE(parsed_message.invalidation_message != NULL); + ASSERT_TRUE(parsed_message.registration_status_message != NULL); + ASSERT_TRUE(parsed_message.registration_sync_request_message != NULL); + ASSERT_TRUE(parsed_message.info_request_message != NULL); +} + +// Test that the protocol handler drops an invalid message. +TEST_F(ProtocolHandlerTest, InvalidInboundMessage) { + // Make an invalid message (omit protocol version from header). + ServerToClientMessage message; + string token = "test token"; + ServerHeader* header = message.mutable_header(); + InitServerHeader(token, header); + header->clear_protocol_version(); + + // Add an info request message to check that it doesn't get processed. + message.mutable_info_request_message()->add_info_type( + InfoRequestMessage_InfoType_GET_PERFORMANCE_COUNTERS); + ParsedMessage parsed_message; + ProcessMessage(message, &parsed_message); + ASSERT_EQ(1, statistics->GetClientErrorCounterForTest( + Statistics::ClientErrorType_INCOMING_MESSAGE_FAILURE)); +} + +// Test that the protocol handler drops a message whose major version doesn't +// match what it understands. +TEST_F(ProtocolHandlerTest, MajorVersionMismatch) { + // Make a message with a different protocol major version. + ServerToClientMessage message; + token = "test token"; + ServerHeader* header = message.mutable_header(); + InitServerHeader(token, header); + header->mutable_protocol_version()->mutable_version()->set_major_version(1); + + // Add an info request message to check that it doesn't get processed. + message.mutable_info_request_message()->add_info_type( + InfoRequestMessage_InfoType_GET_PERFORMANCE_COUNTERS); + + ParsedMessage parsed_message; + ProcessMessage(message, &parsed_message); + ASSERT_EQ(1, statistics->GetClientErrorCounterForTest( + Statistics::ClientErrorType_PROTOCOL_VERSION_FAILURE)); +} + +// Test that the protocol handler doesn't drop a message whose minor version +// doesn't match what it understands. +TEST_F(ProtocolHandlerTest, MinorVersionMismatch) { + // Make a message with a different protocol minor version. + ServerToClientMessage message; + token = "test token"; + ServerHeader* header = message.mutable_header(); + InitServerHeader(token, header); + header->mutable_protocol_version()->mutable_version()->set_minor_version(4); + + ServerMessageHeader expected_header; + expected_header.InitFrom(&token, &summary); + + ParsedMessage parsed_message; + ProcessMessage(message, &parsed_message); + ASSERT_TRUE(HeaderEqual(expected_header, parsed_message.header)); + ASSERT_EQ(0, statistics->GetClientErrorCounterForTest( + Statistics::ClientErrorType_PROTOCOL_VERSION_FAILURE)); +} + +// Test that the protocol handler honors a config message (even if the server +// token doesn't match) and does not call any listener methods. +TEST_F(ProtocolHandlerTest, ConfigMessage) { + // Fabricate a config message. + ServerToClientMessage message; + token = "test token"; + InitServerHeader(token, message.mutable_header()); + token = "token-that-should-mismatch"; + + int next_message_delay_ms = 2000 * 1000; + message.mutable_config_change_message()->set_next_message_delay_ms( + next_message_delay_ms); + + ParsedMessage parsed_message; + ProcessMessage(message, &parsed_message); + + // Check that the protocol handler recorded receiving the config change + // message, and that it has updated the next time it will send a message. + ASSERT_EQ(1, statistics->GetReceivedMessageCounterForTest( + Statistics::ReceivedMessageType_CONFIG_CHANGE)); + ASSERT_EQ( + InvalidationClientUtil::GetTimeInMillis( + start_time + TimeDelta::FromMilliseconds(next_message_delay_ms)), + protocol_handler->GetNextMessageSendTimeMsForTest()); + + // Request to send an info message, and check that it doesn't get sent. + vector<pair<string, int> > empty_vector; + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), &ProtocolHandler::SendInfoMessage, + empty_vector, NULL, false, batching_task.get())); + + // Keep simulating passage of time until just before the quiet period ends. + // Nothing should be sent. (The mock network will catch any attempts to send + // and fail the test.) + internal_scheduler->PassTime( + TimeDelta::FromMilliseconds(next_message_delay_ms - 1)); +} + +// Test that the protocol handler properly delivers an error message to the +// listener. +TEST_F(ProtocolHandlerTest, ErrorMessage) { + // Fabricate an error message. + ServerToClientMessage message; + token = "test token"; + InitServerHeader(token, message.mutable_header()); + + // Add an error message. + ErrorMessage::Code error_code = ErrorMessage_Code_AUTH_FAILURE; + string description = "invalid auth token"; + InitErrorMessage(error_code, description, message.mutable_error_message()); + ServerMessageHeader expected_header; + expected_header.InitFrom(&token, &summary); + + // Deliver the message. + ParsedMessage parsed_message; + ProcessMessage(message, &parsed_message); + ASSERT_TRUE(HeaderEqual(expected_header, parsed_message.header)); + ASSERT_TRUE(parsed_message.error_message != NULL); +} + +// Tests that the protocol handler accepts a message from the server if the +// token doesn't match the client's (the caller is responsible for checking +// the token). +TEST_F(ProtocolHandlerTest, TokenMismatch) { + // Create the server message with one token. + token = "test token"; + ServerToClientMessage message; + InitServerHeader(token, message.mutable_header()); + + // Give the client a different token. + token = "token-that-should-mismatch"; + + // Deliver the message. + ParsedMessage parsed_message; + bool accepted = ProcessMessage(message, &parsed_message); + ASSERT_TRUE(accepted); + + ASSERT_EQ(0, statistics->GetClientErrorCounterForTest( + Statistics::ClientErrorType_TOKEN_MISMATCH)); +} + +// Tests that the protocol handler won't send out a non-initialize message if +// the client has no token. +TEST_F(ProtocolHandlerTest, TokenMissing) { + token = ""; + vector<pair<string, int> > empty_vector; + + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), + &ProtocolHandler::SendInfoMessage, empty_vector, NULL, true, + batching_task.get())); + + internal_scheduler->PassTime(GetMaxBatchingDelay(config)); + + ASSERT_EQ(1, statistics->GetClientErrorCounterForTest( + Statistics::ClientErrorType_TOKEN_MISSING_FAILURE)); +} + +// Tests that the protocol handler won't send out a message that fails +// validation (in this case, an invalidation ack with a missing version). +TEST_F(ProtocolHandlerTest, InvalidOutboundMessage) { + token = "test token"; + + vector<ObjectIdP> object_ids; + InitTestObjectIds(1, &object_ids); + vector<InvalidationP> invalidations; + MakeInvalidationsFromObjectIds(object_ids, &invalidations); + invalidations[0].clear_version(); + + internal_scheduler->Schedule( + Scheduler::NoDelay(), + NewPermanentCallback( + protocol_handler.get(), + &ProtocolHandler::SendInvalidationAck, + invalidations[0], + batching_task.get())); + + internal_scheduler->PassTime(GetMaxBatchingDelay(config)); + + ASSERT_EQ(1, statistics->GetClientErrorCounterForTest( + Statistics::ClientErrorType_OUTGOING_MESSAGE_FAILURE)); +} + +// Tests that the protocol handler drops an unparseable message. +TEST_F(ProtocolHandlerTest, UnparseableInboundMessage) { + // Make an unparseable message. + string serialized = "this can't be a valid protocol buffer!"; + ParsedMessage parsed_message; + bool accepted = protocol_handler->HandleIncomingMessage(serialized, + &parsed_message); + ASSERT_FALSE(accepted); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task.cc new file mode 100644 index 0000000..3196c8f --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task.cc @@ -0,0 +1,81 @@ +// Copyright 2012 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. + +// An abstraction for scheduling recurring tasks. +// + +#include "google/cacheinvalidation/impl/recurring-task.h" +#include "google/cacheinvalidation/impl/log-macro.h" + +namespace invalidation { + +RecurringTask::RecurringTask(string name, Scheduler* scheduler, Logger* logger, + Smearer* smearer, ExponentialBackoffDelayGenerator* delay_generator, + TimeDelta initial_delay, TimeDelta timeout_delay) : name_(name), + scheduler_(scheduler), logger_(logger), smearer_(smearer), + delay_generator_(delay_generator), initial_delay_(initial_delay), + timeout_delay_(timeout_delay), is_scheduled_(false) { +} + +void RecurringTask::EnsureScheduled(string debug_reason) { + RecurringTask::EnsureScheduled(false, debug_reason); +} + +void RecurringTask::EnsureScheduled(bool is_retry, string debug_reason) { + CHECK(scheduler_->IsRunningOnThread()); + if (is_scheduled_) { + return; + } + TimeDelta delay; + if (is_retry) { + // For a retried task, determine the delay to be timeout + extra delay + // (depending on whether a delay generator was provided or not). + if (delay_generator_.get() != NULL) { + delay = timeout_delay_ + delay_generator_->GetNextDelay(); + } else { + delay = timeout_delay_ + smearer_->GetSmearedDelay(initial_delay_); + } + } else { + delay = smearer_->GetSmearedDelay(initial_delay_); + } + + TLOG(logger_, FINE, "[%s] Scheduling %d with a delay %d, Now = %d", + debug_reason.c_str(), name_.c_str(), delay.ToInternalValue(), + scheduler_->GetCurrentTime().ToInternalValue()); + scheduler_->Schedule(delay, NewPermanentCallback(this, + &RecurringTask::RunTaskAndRescheduleIfNeeded)); + is_scheduled_ = true; +} + +void RecurringTask::RunTaskAndRescheduleIfNeeded() { + CHECK(scheduler_->IsRunningOnThread()) << "Not on scheduler thread"; + is_scheduled_ = false; + + // Run the task. If the task asks for a retry, reschedule it after at a + // timeout delay. Otherwise, resets the delay_generator. + if (RunTask()) { + // The task asked to be rescheduled, so reschedule it after a timeout has + // occurred. + CHECK((delay_generator_ != NULL) || + (initial_delay_ > Scheduler::NoDelay())) + << "Spinning: No exp back off and initial delay is zero"; + EnsureScheduled(true, "Retry"); + } else if (delay_generator_ != NULL) { + // The task asked not to be rescheduled. Treat it as having "succeeded" + // and reset the delay generator. + delay_generator_->Reset(); + } +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task.h new file mode 100644 index 0000000..492a0f0 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task.h @@ -0,0 +1,128 @@ +// Copyright 2012 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. + +// +// An abstraction for scheduling recurring tasks. Combines idempotent scheduling +// and smearing with conditional retries and exponential backoff. Does not +// implement throttling. Designed to support a variety of use cases, including +// the following capabilities. +// +// * Idempotent scheduling, e.g., ensuring that a batching task is scheduled +// exactly once. +// * Recurring tasks, e.g., periodic heartbeats. +// * Retriable actions aimed at state change, e.g., sending initialization +// messages. +// +// Each instance of this class manages the state for a single task. See the +// invalidation-client-impl.cc for examples. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_RECURRING_TASK_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_RECURRING_TASK_H_ + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/impl/exponential-backoff-delay-generator.h" +#include "google/cacheinvalidation/impl/smearer.h" + +namespace invalidation { + +class RecurringTask { + public: + /* Creates a recurring task with the given parameters. The specs of the + * parameters are given in the instance variables. + * + * The created task is first scheduled with a smeared delay of + * |initial_delay|. If the |this->run()| returns true on its execution, the + * task is rescheduled after a |timeout_delay| + smeared delay of + * |initial_delay| or |timeout_delay| + |delay_generator->GetNextDelay()| + * depending on whether the |delay_generator| is null or not. + * + * Space for |scheduler|, |logger|, |smearer| is owned by the caller. + * Space for |delay_generator| is owned by the callee. + */ + RecurringTask(string name, Scheduler* scheduler, Logger* logger, + Smearer* smearer, ExponentialBackoffDelayGenerator* delay_generator, + TimeDelta initial_delay, TimeDelta timeout_delay); + + virtual ~RecurringTask() {} + + /* Run the task and return true if the task should be rescheduled after a + * timeout. If false is returned, the task is not scheduled again until + * |ensure_scheduled| is called again. + */ + virtual bool RunTask() = 0; + + /* Ensures that the task is scheduled (with |debug_reason| as the reason to be + * printed for debugging purposes). If the task has been scheduled, it is + * not scheduled again. + * + * REQUIRES: Must be called from the scheduler thread. + */ + void EnsureScheduled(string debug_reason); + + /* Space for the returned Smearer is still owned by this class. */ + Smearer* smearer() { + return smearer_; + } + + private: + /* Run the task and check if it needs to be rescheduled. If so, reschedule it + * after the appropriate delay. + */ + void RunTaskAndRescheduleIfNeeded(); + + /* Ensures that the task is scheduled if it is already not scheduled. If + * already scheduled, this method is a no-op. If |is_retry| is |false|, smears + * the |initial_delay_| and uses that delay for scheduling. If |is_retry| is + * true, it determines the new delay to be + * |timeout_delay_ + delay_generator.GetNextDelay()| if |delay_generator| is + * non-null. If |delay_generator| is null, schedules the task after a delay of + * |timeout_delay_| + smeared value of |initial_delay_|. + * + * REQUIRES: Must be called from the scheduler thread. + */ + void EnsureScheduled(bool is_retry, string debug_reason); + + /* Name of the task (for debugging purposes mostly). */ + string name_; + + /* Scheduler for the scheduling the task as needed. */ + Scheduler* scheduler_; + + /* A logger. */ + Logger* logger_; + + /* A smearer for spreading the delays. */ + Smearer* smearer_; + + /* A delay generator for exponential backoff. */ + scoped_ptr<ExponentialBackoffDelayGenerator> delay_generator_; + + /* + * The time after which the task is scheduled first. If no delayGenerator is + * specified, this is also the delay used for retries. + */ + TimeDelta initial_delay_; + + /* For a task that is retried, add this time to the delay. */ + TimeDelta timeout_delay_; + + /* If the task has been currently scheduled. */ + bool is_scheduled_; + + DISALLOW_COPY_AND_ASSIGN(RecurringTask); +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_RECURRING_TASK_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task_test.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task_test.cc new file mode 100644 index 0000000..cbe45e21 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task_test.cc @@ -0,0 +1,236 @@ +// Copyright 2012 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. + +// Unit tests for the RecurringTask class. + +#include "google/cacheinvalidation/client_test_internal.pb.h" +#include "google/cacheinvalidation/deps/googletest.h" +#include "google/cacheinvalidation/deps/random.h" +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/impl/recurring-task.h" +#include "google/cacheinvalidation/test/deterministic-scheduler.h" +#include "google/cacheinvalidation/test/test-logger.h" +#include "google/cacheinvalidation/test/test-utils.h" + +namespace invalidation { + +class RecurringTaskTest; + +/* A Test task that tracks how many times it has been called and returns + * true when the number of times, it has been called is less than the expected + * number. + */ +class TestTask : public RecurringTask { + public: + /* Initial delay used by the TestTask. */ + static TimeDelta initial_delay; + + /* Timeout delay used by the TestTask. */ + static TimeDelta timeout_delay; + + /* Creates a test task. + * + * |scheduler| Scheduler for the scheduling the task as needed. + * |logger| A logger. + * |smearer| For spreading/randomizing the delays. + * |delay_generator| An exponential backoff generator for task retries (if + * any). + * |test_name| The name of the current test. + * |max_runs| Maximum number of runs that are allowed. + * + * Space for all the objects with pointers is owned by the caller. + */ + TestTask(Scheduler* scheduler, Logger* logger, Smearer* smearer, + ExponentialBackoffDelayGenerator* delay_generator, + const string& test_name, int max_runs) + : RecurringTask(test_name, scheduler, logger, smearer, delay_generator, + initial_delay, timeout_delay), + current_runs(0), + max_runs_(max_runs), + scheduler_(scheduler), + logger_(logger) { + } + + virtual ~TestTask() {} + + // The actual implementation as required by the RecurringTask. + virtual bool RunTask() { + current_runs++; + TLOG(logger_, INFO, "(%d) Task running: %d", + scheduler_->GetCurrentTime().ToInternalValue(), current_runs); + return current_runs < max_runs_; + } + + /* The number of times that the task has been run. */ + int current_runs; + + private: + /* Maximum number of runs that are allowed. */ + int max_runs_; + + /* Scheduler for the task. */ + Scheduler* scheduler_; + + /* A logger. */ + Logger* logger_; +}; + +// Tests the basic functionality of the RecurringTask abstraction. +class RecurringTaskTest : public testing::Test { + public: + virtual ~RecurringTaskTest() {} + + // Performs setup for RecurringTask test. + virtual void SetUp() { + // Initialize values that are really constants. + initial_exp_backoff_delay = TimeDelta::FromMilliseconds(100); + TestTask::initial_delay = TimeDelta::FromMilliseconds(10); + TestTask::timeout_delay = TimeDelta::FromMilliseconds(50); + end_of_test_delay = 1000 * TestTask::timeout_delay; + + // Initialize state for every test. + random.reset(new FakeRandom(0.99)); // The test expects a value close to 1. + logger.reset(new TestLogger()); + scheduler.reset(new SimpleDeterministicScheduler(logger.get())); + smearer.reset(new Smearer(random.get(), 0)); + delay_generator = new ExponentialBackoffDelayGenerator( + random.get(), initial_exp_backoff_delay, kMaxExpBackoffFactor); + scheduler->StartScheduler(); + } + + /* Maximum delay factory used by the ExponentialBackoffDelayGenerator. */ + static const int kMaxExpBackoffFactor; + + /* Default number of runs that runTask is called in TestTask. */ + static const int kDefaultNumRuns; + + /* Initial delay used by the ExponentialBackoffDelayGenerator. */ + static TimeDelta initial_exp_backoff_delay; + + /* A long time delay that the scheduler is run for at the end of the test. */ + static TimeDelta end_of_test_delay; + + // + // Test state maintained for every test. + // + + // A logger. + scoped_ptr<Logger> logger; + + // Deterministic scheduler for careful scheduling of the tasks. + scoped_ptr<DeterministicScheduler> scheduler; + + // For randomizing delays. + scoped_ptr<Smearer> smearer; + + // A random number generator that always generates 1. + scoped_ptr<Random> random; + + // A delay generator (if used in the test). Not a scoped_ptr since it ends + // up being owned by the RecurringTask. + ExponentialBackoffDelayGenerator* delay_generator; +}; + +// Definitions for the static variables. +TimeDelta TestTask::initial_delay; +TimeDelta TestTask::timeout_delay; +TimeDelta RecurringTaskTest::initial_exp_backoff_delay; +TimeDelta RecurringTaskTest::end_of_test_delay; +const int RecurringTaskTest::kMaxExpBackoffFactor = 10; +const int RecurringTaskTest::kDefaultNumRuns = 8; + +/* Tests a task that is run periodically at regular intervals (not exponential + * backoff). + */ +TEST_F(RecurringTaskTest, PeriodicTask) { + /* Create a periodic task and pass time - make sure that the task runs exactly + * the number of times as expected. + */ + TestTask task(scheduler.get(), logger.get(), smearer.get(), NULL, + "PeriodicTask", kDefaultNumRuns); + task.EnsureScheduled("testPeriodicTask"); + + TimeDelta delay = TestTask::initial_delay + TestTask::timeout_delay; + + // Pass time so that the task is run kDefaultNumRuns times. + // First time, the task is scheduled after initial_delay. Then for + // numRuns - 1, it is scheduled after a delay of + // initial_delay + timeout_delay. + scheduler->PassTime(TestTask::initial_delay + + ((kDefaultNumRuns - 1) * delay)); + ASSERT_EQ(kDefaultNumRuns, task.current_runs); + + // Check that the passage of more time does not cause any more runs. + scheduler->PassTime(end_of_test_delay); + ASSERT_EQ(kDefaultNumRuns, task.current_runs); + delete delay_generator; +} + +/* Tests a task that is run periodically at regular intervals with + * exponential backoff. + */ +TEST_F(RecurringTaskTest, ExponentialBackoffTask) { + /* Create a periodic task and pass time - make sure that the task runs + * exactly the number of times as expected. + */ + TestTask task(scheduler.get(), logger.get(), smearer.get(), + delay_generator, "ExponentialBackoffTask", kDefaultNumRuns); + task.EnsureScheduled("testExponentialBackoffTask"); + + // Pass enough time so that exactly one event runs, two events run etc. + scheduler->PassTime(TestTask::initial_delay); + ASSERT_EQ(1, task.current_runs); + scheduler->PassTime(TestTask::timeout_delay + initial_exp_backoff_delay); + ASSERT_EQ(2, task.current_runs); + scheduler->PassTime( + TestTask::timeout_delay + (2 * initial_exp_backoff_delay)); + ASSERT_EQ(3, task.current_runs); + scheduler->PassTime( + TestTask::timeout_delay + (4 * initial_exp_backoff_delay)); + ASSERT_EQ(4, task.current_runs); + + // Check that the passage of more time does not cause any more runs. + scheduler->PassTime(end_of_test_delay); + ASSERT_EQ(kDefaultNumRuns, task.current_runs); +} + +/* Tests a one-shot task (i.e. no repetition) that is run twice. */ +TEST_F(RecurringTaskTest, OneShotTask) { + /* Create a no-repeating task and pass time - make sure that the task runs + * exactly once. Run it again - and make sure it is run again. + */ + + // Call ensureScheduled multiple times; ensure that the event is not scheduled + // multiple times. + TestTask task(scheduler.get(), logger.get(), smearer.get(), + delay_generator, "OneShotTask", 1); + task.EnsureScheduled("testOneShotTask"); + task.EnsureScheduled("testOneShotTask-2"); + task.EnsureScheduled("testOneShotTask-3"); + + // Pass enough time so that exactly one event runs. + scheduler->PassTime(TestTask::initial_delay); + ASSERT_EQ(1, task.current_runs); + + // Pass enough time so that exactly another event runs. + task.EnsureScheduled("testOneShotTask-4"); + scheduler->PassTime(TestTask::initial_delay); + ASSERT_EQ(2, task.current_runs); + + // Check that the passage of more time does not cause any more runs. + scheduler->PassTime(end_of_test_delay); + ASSERT_EQ(2, task.current_runs); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/registration-manager.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/registration-manager.cc new file mode 100644 index 0000000..3e4574e --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/registration-manager.cc @@ -0,0 +1,135 @@ +// Copyright 2012 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. + +// Object to track desired client registrations. This class belongs to caller +// (e.g., InvalidationClientImpl) and is not thread-safe - the caller has to use +// this class in a thread-safe manner. + +#include "google/cacheinvalidation/impl/registration-manager.h" + +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" +#include "google/cacheinvalidation/impl/log-macro.h" +#include "google/cacheinvalidation/impl/proto-helpers.h" +#include "google/cacheinvalidation/impl/simple-registration-store.h" + +namespace invalidation { + +RegistrationManager::RegistrationManager( + Logger* logger, Statistics* statistics, DigestFunction* digest_function) + : desired_registrations_(new SimpleRegistrationStore(digest_function)), + statistics_(statistics), + logger_(logger) { + // Initialize the server summary with a 0 size and the digest corresponding to + // it. Using defaultInstance would wrong since the server digest will not + // match unnecessarily and result in an info message being sent. + GetClientSummary(&last_known_server_summary_); +} + +void RegistrationManager::PerformOperations( + const vector<ObjectIdP>& object_ids, RegistrationP::OpType reg_op_type, + vector<ObjectIdP>* oids_to_send) { + // Record that we have pending operations on the objects. + vector<ObjectIdP>::const_iterator iter = object_ids.begin(); + for (; iter != object_ids.end(); iter++) { + pending_operations_[*iter] = reg_op_type; + } + // Update the digest appropriately. + if (reg_op_type == RegistrationP_OpType_REGISTER) { + desired_registrations_->Add(object_ids, oids_to_send); + } else { + desired_registrations_->Remove(object_ids, oids_to_send); + } +} + +void RegistrationManager::GetRegistrations( + const string& digest_prefix, int prefix_len, RegistrationSubtree* builder) { + vector<ObjectIdP> oids; + desired_registrations_->GetElements(digest_prefix, prefix_len, &oids); + for (size_t i = 0; i < oids.size(); ++i) { + builder->add_registered_object()->CopyFrom(oids[i]); + } +} + +void RegistrationManager::HandleRegistrationStatus( + const RepeatedPtrField<RegistrationStatus>& registration_statuses, + vector<bool>* success_status) { + + // Local-processing result code for each element of + // registrationStatuses. Indicates whether the registration status was + // compatible with the client's desired state (e.g., a successful unregister + // from the server when we desire a registration is incompatible). + for (int i = 0; i < registration_statuses.size(); ++i) { + const RegistrationStatus& registration_status = + registration_statuses.Get(i); + const ObjectIdP& object_id_proto = + registration_status.registration().object_id(); + + // The object is no longer pending, since we have received a server status + // for it, so remove it from the pendingOperations map. (It may or may not + // have existed in the map, since we can receive spontaneous status messages + // from the server.) + pending_operations_.erase(object_id_proto); + + // We start off with the local-processing set as success, then potentially + // fail. + bool is_success = true; + + // if the server operation succeeded, then local processing fails on + // "incompatibility" as defined above. + if (registration_status.status().code() == StatusP_Code_SUCCESS) { + bool app_wants_registration = + desired_registrations_->Contains(object_id_proto); + bool is_op_registration = + (registration_status.registration().op_type() == + RegistrationP_OpType_REGISTER); + bool discrepancy_exists = is_op_registration ^ app_wants_registration; + if (discrepancy_exists) { + // Remove the registration and set isSuccess to false, which will cause + // the caller to issue registration-failure to the application. + desired_registrations_->Remove(object_id_proto); + statistics_->RecordError( + Statistics::ClientErrorType_REGISTRATION_DISCREPANCY); + TLOG(logger_, INFO, + "Ticl discrepancy detected: registered = %d, requested = %d. " + "Removing %s from requested", + is_op_registration, app_wants_registration, + ProtoHelpers::ToString(object_id_proto).c_str()); + is_success = false; + } + } else { + // If the server operation failed, then local processing also fails. + desired_registrations_->Remove(object_id_proto); + TLOG(logger_, FINE, "Removing %s from committed", + ProtoHelpers::ToString(object_id_proto).c_str()); + is_success = false; + } + success_status->push_back(is_success); + } +} + +void RegistrationManager::GetClientSummary(RegistrationSummary* summary) { + summary->set_num_registrations(desired_registrations_->size()); + summary->set_registration_digest(desired_registrations_->GetDigest()); +} + +string RegistrationManager::ToString() { + return StringPrintf( + "Last known digest: %s, Requested regs: %s", + ProtoHelpers::ToString(last_known_server_summary_).c_str(), + desired_registrations_->ToString().c_str()); +} + +const char* RegistrationManager::kEmptyPrefix = ""; + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/registration-manager.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/registration-manager.h new file mode 100644 index 0000000..3490399 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/registration-manager.h @@ -0,0 +1,198 @@ +// Copyright 2012 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. + +// Object to track desired client registrations. This class belongs to caller +// (e.g., InvalidationClientImpl) and is not thread-safe - the caller has to use +// this class in a thread-safe manner. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_REGISTRATION_MANAGER_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_REGISTRATION_MANAGER_H_ + +#include <map> +#include <set> + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/digest-function.h" +#include "google/cacheinvalidation/deps/scoped_ptr.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" +#include "google/cacheinvalidation/impl/digest-store.h" +#include "google/cacheinvalidation/impl/proto-helpers.h" +#include "google/cacheinvalidation/impl/statistics.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::map; +using INVALIDATION_STL_NAMESPACE::set; + +class RegistrationManager { + public: + RegistrationManager(Logger* logger, Statistics* statistics, + DigestFunction* digest_function); + + /* Sets the digest store to be digest_store for testing purposes. + * + * REQUIRES: This method is called before the Ticl has done any operations on + * this object. + */ + void SetDigestStoreForTest(DigestStore<ObjectIdP>* digest_store) { + desired_registrations_.reset(digest_store); + GetClientSummary(&last_known_server_summary_); + } + + void GetRegisteredObjectsForTest(vector<ObjectIdP>* registrations) { + desired_registrations_->GetElements(kEmptyPrefix, 0, registrations); + } + + /* (Un)registers for object_ids. When the function returns, oids_to_send will + * have been modified to contain those object ids for which registration + * messages must be sent to the server. + */ + void PerformOperations(const vector<ObjectIdP>& object_ids, + RegistrationP::OpType reg_op_type, + vector<ObjectIdP>* oids_to_send); + + /* Initializes a registration subtree for registrations where the digest of + * the object id begins with the prefix digest_prefix of prefix_len bits. This + * method may also return objects whose digest prefix does not match + * digest_prefix. + */ + void GetRegistrations(const string& digest_prefix, int prefix_len, + RegistrationSubtree* builder); + + /* + * Handles registration operation statuses from the server. Modifies |result| + * to contain one boolean per registration status, that indicates whether the + * registration operation was both successful and agreed with the desired + * client state (i.e., for each registration status, + * (status.optype == register) == + * desiredRegistrations.contains(status.objectid)). + * <p> + * REQUIRES: the caller subsequently make an informRegistrationStatus or + * informRegistrationFailure upcall on the listener for each registration in + * {@code registrationStatuses}. + */ + void HandleRegistrationStatus( + const RepeatedPtrField<RegistrationStatus>& registration_statuses, + vector<bool>* result); + + /* + * Removes all desired registrations and pending operations. Returns all + * object ids that were affected. + * <p> + * REQUIRES: the caller issue a permanent failure upcall to the listener for + * all returned object ids. + */ + void RemoveRegisteredObjects(vector<ObjectIdP>* result) { + // Add the formerly desired- and pending- registrations to result. + desired_registrations_->RemoveAll(result); + map<ObjectIdP, RegistrationP::OpType, ProtoCompareLess>::iterator + pending_iter = pending_operations_.begin(); + for (; pending_iter != pending_operations_.end(); pending_iter++) { + result->push_back(pending_iter->first); + } + pending_operations_.clear(); + + // De-dup result. + set<ObjectIdP, ProtoCompareLess> unique_oids(result->begin(), + result->end()); + result->assign(unique_oids.begin(), unique_oids.end()); + } + + // + // Digest-related methods + // + + /* Modifies client_summary to contain the summary of the desired + * registrations (by the client). */ + void GetClientSummary(RegistrationSummary* client_summary); + + /* Modifies server_summary to contain the last known summary from the server. + * If none, modifies server_summary to contain the summary corresponding + * to 0 registrations. */ + void GetServerSummary(RegistrationSummary* server_summary) { + server_summary->CopyFrom(last_known_server_summary_); + } + + /* Informs the manager of a new registration state summary from the server. + * Modifies upcalls to contain zero or more RegistrationP. For each added + * RegistrationP, the caller should make an inform-registration-status upcall + * on the listener. + */ + void InformServerRegistrationSummary(const RegistrationSummary& reg_summary, + vector<RegistrationP>* upcalls) { + last_known_server_summary_.CopyFrom(reg_summary); + if (IsStateInSyncWithServer()) { + // If we are now in sync with the server, then the caller should make + // inform-reg-status upcalls for all operations that we had pending, if + // any; they are also no longer pending. + map<ObjectIdP, RegistrationP::OpType, ProtoCompareLess>::iterator + pending_iter = pending_operations_.begin(); + for (; pending_iter != pending_operations_.end(); pending_iter++) { + RegistrationP reg_p; + ProtoHelpers::InitRegistrationP(pending_iter->first, + pending_iter->second, ®_p); + upcalls->push_back(reg_p); + } + pending_operations_.clear(); + } + } + + /* Returns whether the local registration state and server state agree, based + * on the last received server summary (from InformServerRegistrationSummary). + */ + bool IsStateInSyncWithServer() { + RegistrationSummary summary; + GetClientSummary(&summary); + return (last_known_server_summary_.num_registrations() == + summary.num_registrations()) && + (last_known_server_summary_.registration_digest() == + summary.registration_digest()); + } + + string ToString(); + + // Empty hash prefix. + static const char* kEmptyPrefix; + + private: + /* The set of regisrations that the application has requested for. */ + scoped_ptr<DigestStore<ObjectIdP> > desired_registrations_; + + /* Statistics objects to track number of sent messages, etc. */ + Statistics* statistics_; + + /* Latest known server registration state summary. */ + RegistrationSummary last_known_server_summary_; + + /* + * Map of object ids and operation types for which we have not yet issued any + * registration-status upcall to the listener. We need this so that we can + * synthesize success upcalls if registration sync, rather than a server + * message, communicates to us that we have a successful (un)registration. + * <p> + * This is a map from object id to type, rather than a set of RegistrationP, + * because a set of RegistrationP would assume that we always get a response + * for every operation we issue, which isn't necessarily true (i.e., the + * server might send back an unregistration status in response to a + * registration request). + */ + map<ObjectIdP, RegistrationP::OpType, ProtoCompareLess> + pending_operations_; + + Logger* logger_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_REGISTRATION_MANAGER_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/repeated-field-namespace-fix.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/repeated-field-namespace-fix.h new file mode 100644 index 0000000..94ba25a --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/repeated-field-namespace-fix.h @@ -0,0 +1,30 @@ +// Copyright 2013 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. + +// +// Brings RepeatedField classes into invalidation namespace. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_REPEATED_FIELD_NAMESPACE_FIX_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_REPEATED_FIELD_NAMESPACE_FIX_H_ + +#include "google/protobuf/repeated_field.h" + +namespace invalidation { + +using ::google::protobuf::RepeatedField; +using ::google::protobuf::RepeatedPtrField; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_REPEATED_FIELD_NAMESPACE_FIX_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/run-state.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/run-state.h new file mode 100644 index 0000000..f330438 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/run-state.h @@ -0,0 +1,87 @@ +// Copyright 2012 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. + +// An abstraction that keeps track of whether the caller is started or stopped +// and only allows the following transitions NOT_STARTED -> STARTED -> +// STOPPED. This class is thread-safe. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_RUN_STATE_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_RUN_STATE_H_ + +#include "google/cacheinvalidation/client.pb.h" +#include "google/cacheinvalidation/deps/logging.h" +#include "google/cacheinvalidation/deps/mutex.h" +#include "google/cacheinvalidation/deps/string_util.h" + +namespace invalidation { + +using ::ipc::invalidation::RunStateP_State; +using ::ipc::invalidation::RunStateP_State_NOT_STARTED; +using ::ipc::invalidation::RunStateP_State_STARTED; +using ::ipc::invalidation::RunStateP_State_STOPPED; + +class RunState { + public: + RunState() : current_state_(RunStateP_State_NOT_STARTED) {} + + /* Marks the current state to be STARTED. + * + * REQUIRES: Current state is NOT_STARTED. + */ + void Start() { + MutexLock m(&lock_); + CHECK(current_state_ == RunStateP_State_NOT_STARTED) << "Cannot start: " + << current_state_; + current_state_ = RunStateP_State_STARTED; + } + + /* Marks the current state to be STOPPED. + * + * REQUIRES: Current state is STARTED. + */ + void Stop() { + MutexLock m(&lock_); + CHECK(current_state_ == RunStateP_State_STARTED) << "Cannot stop: " + << current_state_; + current_state_ = RunStateP_State_STOPPED; + } + + /* Returns true iff Start has been called on this but Stop has not been + * called. + */ + bool IsStarted() const { + // Don't treat locking a mutex as mutation. + MutexLock m((Mutex *) &lock_); // NOLINT + return current_state_ == RunStateP_State_STARTED; + } + + /* Returns true iff Start and Stop have been called on this object. */ + bool IsStopped() const { + // Don't treat locking a mutex as mutation. + MutexLock m((Mutex *) &lock_); // NOLINT + return current_state_ == RunStateP_State_STOPPED; + } + + string ToString() { + return StringPrintf("<RunState %d>", current_state_); + } + + private: + RunStateP_State current_state_; + Mutex lock_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_RUN_STATE_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/safe-storage.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/safe-storage.cc new file mode 100644 index 0000000..f418fbd --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/safe-storage.cc @@ -0,0 +1,74 @@ +// Copyright 2012 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. + +// An implementation of the Storage resource that schedules the callbacks on the +// given scheduler thread. +// + +#include "google/cacheinvalidation/impl/safe-storage.h" + +namespace invalidation { + +void SafeStorage::SetSystemResources(SystemResources* resources) { + scheduler_ = resources->internal_scheduler(); +} + +void SafeStorage::WriteKey(const string& key, const string& value, + WriteKeyCallback* done) { + delegate_->WriteKey(key, value, + NewPermanentCallback(this, &SafeStorage::WriteCallback, done)); +} + +void SafeStorage::WriteCallback(WriteKeyCallback* done, Status status) { + scheduler_->Schedule( + Scheduler::NoDelay(), + /* Owns 'done'. */ NewPermanentCallback(done, status)); +} + +void SafeStorage::ReadKey(const string& key, ReadKeyCallback* done) { + delegate_->ReadKey(key, + NewPermanentCallback(this, &SafeStorage::ReadCallback, done)); +} + +void SafeStorage::ReadCallback(ReadKeyCallback* done, + StatusStringPair read_result) { + scheduler_->Schedule( + Scheduler::NoDelay(), + /* Owns 'done'. */ NewPermanentCallback(done, read_result)); +} + +void SafeStorage::DeleteKey(const string& key, DeleteKeyCallback* done) { + delegate_->DeleteKey(key, + NewPermanentCallback(this, &SafeStorage::DeleteCallback, done)); +} + +void SafeStorage::DeleteCallback(DeleteKeyCallback* done, bool result) { + scheduler_->Schedule( + Scheduler::NoDelay(), + /* Owns 'done'. */ NewPermanentCallback(done, result)); +} + +void SafeStorage::ReadAllKeys(ReadAllKeysCallback* key_callback) { + delegate_->ReadAllKeys( + NewPermanentCallback(this, &SafeStorage::ReadAllCallback, key_callback)); +} + +void SafeStorage::ReadAllCallback(ReadAllKeysCallback* key_callback, + StatusStringPair result) { + scheduler_->Schedule( + Scheduler::NoDelay(), + /* Owns 'key_callback'. */ NewPermanentCallback(key_callback, result)); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/safe-storage.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/safe-storage.h new file mode 100644 index 0000000..b6ec2dc --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/safe-storage.h @@ -0,0 +1,72 @@ +// Copyright 2012 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. + +// +// An implementation of the Storage resource that schedules the callbacks on the +// given scheduler thread. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_SAFE_STORAGE_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_SAFE_STORAGE_H_ + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/include/types.h" + +namespace invalidation { + +// An implementation of the Storage resource that schedules the callbacks on the +// given scheduler thread. +class SafeStorage : public Storage { + public: + /* Creates a new instance. Storage for |delegate| is owned by caller. */ + explicit SafeStorage(Storage* delegate) : delegate_(delegate) { + } + + virtual ~SafeStorage() {} + + // All public methods below are methods of the Storage interface. + virtual void SetSystemResources(SystemResources* resources); + + virtual void WriteKey(const string& key, const string& value, + WriteKeyCallback* done); + + virtual void ReadKey(const string& key, ReadKeyCallback* done); + + virtual void DeleteKey(const string& key, DeleteKeyCallback* done); + + virtual void ReadAllKeys(ReadAllKeysCallback* key_callback); + + private: + /* Callback invoked when WriteKey finishes. */ + void WriteCallback(WriteKeyCallback* done, Status status); + + /* Callback invoked when ReadKey finishes. */ + void ReadCallback(ReadKeyCallback* done, StatusStringPair read_result); + + /* Callback invoked when DeleteKey finishes. */ + void DeleteCallback(DeleteKeyCallback* done, bool result); + + /* Callback invoked when ReadAllKeys finishes. */ + void ReadAllCallback(ReadAllKeysCallback* key_callback, + StatusStringPair result); + + /* The delegate to which the calls are forwarded. */ + Storage* delegate_; + + /* The scheduler on which the callbacks are scheduled. */ + Scheduler* scheduler_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_SAFE_STORAGE_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/simple-registration-store.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/simple-registration-store.cc new file mode 100644 index 0000000..98d284a --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/simple-registration-store.cc @@ -0,0 +1,107 @@ +// Copyright 2012 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. + +// Simple, map-based implementation of DigestStore. + +#include "google/cacheinvalidation/impl/simple-registration-store.h" + +#include "google/cacheinvalidation/impl/object-id-digest-utils.h" + +namespace invalidation { + +bool SimpleRegistrationStore::Add(const ObjectIdP& oid) { + const string digest = ObjectIdDigestUtils::GetDigest(oid, digest_function_); + bool will_add = (registrations_.find(digest) == registrations_.end()); + if (will_add) { + registrations_[digest] = oid; + RecomputeDigest(); + } + return will_add; +} + +void SimpleRegistrationStore::Add(const vector<ObjectIdP>& oids, + vector<ObjectIdP>* oids_to_send) { + for (size_t i = 0; i < oids.size(); ++i) { + const ObjectIdP& oid = oids[i]; + const string digest = ObjectIdDigestUtils::GetDigest(oid, digest_function_); + bool will_add = (registrations_.find(digest) == registrations_.end()); + if (will_add) { + registrations_[digest] = oid; + oids_to_send->push_back(oid); + } + } + if (!oids_to_send->empty()) { + // Only recompute the digest if we made changes. + RecomputeDigest(); + } +} + +bool SimpleRegistrationStore::Remove(const ObjectIdP& oid) { + const string digest = ObjectIdDigestUtils::GetDigest(oid, digest_function_); + bool will_remove = (registrations_.find(digest) != registrations_.end()); + if (will_remove) { + registrations_.erase(digest); + RecomputeDigest(); + } + return will_remove; +} + +void SimpleRegistrationStore::Remove(const vector<ObjectIdP>& oids, + vector<ObjectIdP>* oids_to_send) { + for (size_t i = 0; i < oids.size(); ++i) { + const ObjectIdP& oid = oids[i]; + const string digest = ObjectIdDigestUtils::GetDigest(oid, digest_function_); + bool will_remove = (registrations_.find(digest) != registrations_.end()); + if (will_remove) { + registrations_.erase(digest); + oids_to_send->push_back(oid); + } + } + if (!oids_to_send->empty()) { + // Only recompute the digest if we made changes. + RecomputeDigest(); + } +} + +void SimpleRegistrationStore::RemoveAll(vector<ObjectIdP>* oids) { + for (map<string, ObjectIdP>::const_iterator iter = registrations_.begin(); + iter != registrations_.end(); ++iter) { + oids->push_back(iter->second); + } + registrations_.clear(); + RecomputeDigest(); +} + +bool SimpleRegistrationStore::Contains(const ObjectIdP& oid) { + return registrations_.find( + ObjectIdDigestUtils::GetDigest(oid, digest_function_)) != + registrations_.end(); +} + +void SimpleRegistrationStore::GetElements( + const string& oid_digest_prefix, int prefix_len, + vector<ObjectIdP>* result) { + // We always return all the registrations and let the Ticl sort it out. + for (map<string, ObjectIdP>::iterator iter = registrations_.begin(); + iter != registrations_.end(); ++iter) { + result->push_back(iter->second); + } +} + +void SimpleRegistrationStore::RecomputeDigest() { + digest_ = ObjectIdDigestUtils::GetDigest( + registrations_, digest_function_); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/simple-registration-store.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/simple-registration-store.h new file mode 100644 index 0000000..226cb87 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/simple-registration-store.h @@ -0,0 +1,87 @@ +// Copyright 2012 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. + +// Simple, map-based implementation of DigestStore. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_SIMPLE_REGISTRATION_STORE_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_SIMPLE_REGISTRATION_STORE_H_ + +#include <map> + +#include "google/cacheinvalidation/deps/digest-function.h" +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" +#include "google/cacheinvalidation/impl/digest-store.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::map; + +class SimpleRegistrationStore : public DigestStore<ObjectIdP> { + public: + explicit SimpleRegistrationStore(DigestFunction* digest_function) + : digest_function_(digest_function) { + RecomputeDigest(); + } + + virtual ~SimpleRegistrationStore() {} + + virtual bool Add(const ObjectIdP& oid); + + virtual void Add(const vector<ObjectIdP>& oids, + vector<ObjectIdP>* oids_to_send); + + virtual bool Remove(const ObjectIdP& oid); + + virtual void Remove(const vector<ObjectIdP>& oids, + vector<ObjectIdP>* oids_to_send); + + virtual void RemoveAll(vector<ObjectIdP>* oids); + + virtual bool Contains(const ObjectIdP& oid); + + virtual int size() { + return registrations_.size(); + } + + virtual string GetDigest() { + return digest_; + } + + virtual void GetElements(const string& oid_digest_prefix, int prefix_len, + vector<ObjectIdP>* result); + + virtual string ToString() { + return StringPrintf("SimpleRegistrationStore: %d registrations", + static_cast<int>(registrations_.size())); + } + + private: + /* Recomputes the digests over all objects and sets this.digest. */ + void RecomputeDigest(); + + /* All the registrations in the store mappd from the digest to the ibject id. + */ + map<string, ObjectIdP> registrations_; + + /* The function used to compute digests of objects. */ + DigestFunction* digest_function_; + + /* The memoized digest of all objects in registrations. */ + string digest_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_SIMPLE_REGISTRATION_STORE_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/smearer.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/smearer.h new file mode 100644 index 0000000..7fc43f0 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/smearer.h @@ -0,0 +1,62 @@ +// Copyright 2012 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. + +// An abstraction to "smear" values by a given percent. Useful for randomizing +// delays a little bit so that (say) processes do not get synchronized on time +// inadvertently, e.g., a heartbeat task that sends a message every few minutes +// is smeared so that all clients do not end up sending a message at the same +// time. In particular, given a |delay|, returns a value that is randomly +// distributed between +// [delay - smearPercent * delay, delay + smearPercent * delay] + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_SMEARER_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_SMEARER_H_ + +#include "google/cacheinvalidation/deps/logging.h" +#include "google/cacheinvalidation/deps/random.h" +#include "google/cacheinvalidation/deps/scoped_ptr.h" + +namespace invalidation { + +class Smearer { + public: + /* Creates a smearer with the given random number generator. + * REQUIRES: 0 <= smear_percent <= 100 + * Caller continues to own space for random. + */ + Smearer(Random* random, int smear_percent) : random_(random), + smear_fraction_(smear_percent / 100.0) { + CHECK((smear_percent >= 0) && (smear_percent <= 100)); + } + + /* Given a delay, returns a value that is randomly distributed between + * (delay - smear_percent * delay, delay + smear_percent * delay) + */ + TimeDelta GetSmearedDelay(TimeDelta delay) { + // Get a random number between -1 and 1 and then multiply that by the + // smear fraction. + double smear_factor = (2 * random_->RandDouble() - 1.0) * smear_fraction_; + return TimeDelta::FromMilliseconds( + delay.InMilliseconds() * (1.0 + smear_factor)); + } + + private: + Random* random_; + + /* The percentage (0, 1.0] for smearing the delay. */ + double smear_fraction_; +}; +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_SMEARER_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/statistics.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/statistics.cc new file mode 100644 index 0000000..382d45c --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/statistics.cc @@ -0,0 +1,121 @@ +// Copyright 2012 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. + +// Statistics for the Ticl, e.g., number of registration calls, number of token +// mismatches, etc. + +#include "google/cacheinvalidation/impl/statistics.h" + +namespace invalidation { + +const char* Statistics::SentMessageType_names[] = { + "INFO", + "INITIALIZE", + "INVALIDATION_ACK", + "REGISTRATION", + "REGISTRATION_SYNC", + "TOTAL", +}; + +const char* Statistics::ReceivedMessageType_names[] = { + "INFO_REQUEST", + "INVALIDATION", + "REGISTRATION_STATUS", + "REGISTRATION_SYNC_REQUEST", + "TOKEN_CONTROL", + "ERROR", + "CONFIG_CHANGE", + "TOTAL", +}; + +const char* Statistics::IncomingOperationType_names[] = { + "ACKNOWLEDGE", + "REGISTRATION", + "UNREGISTRATION", +}; + +const char* Statistics::ListenerEventType_names[] = { + "INFORM_ERROR", + "INFORM_REGISTRATION_FAILURE", + "INFORM_REGISTRATION_STATUS", + "INVALIDATE", + "INVALIDATE_ALL", + "INVALIDATE_UNKNOWN", + "REISSUE_REGISTRATIONS", +}; + +const char* Statistics::ClientErrorType_names[] = { + "ACKNOWLEDGE_HANDLE_FAILURE", + "INCOMING_MESSAGE_FAILURE", + "OUTGOING_MESSAGE_FAILURE", + "PERSISTENT_DESERIALIZATION_FAILURE", + "PERSISTENT_READ_FAILURE", + "PERSISTENT_WRITE_FAILURE", + "PROTOCOL_VERSION_FAILURE", + "REGISTRATION_DISCREPANCY", + "NONCE_MISMATCH", + "TOKEN_MISMATCH", + "TOKEN_MISSING_FAILURE", + "TOKEN_TRANSIENT_FAILURE", +}; + +Statistics::Statistics() { + InitializeMap(sent_message_types_, SentMessageType_MAX + 1); + InitializeMap(received_message_types_, ReceivedMessageType_MAX + 1); + InitializeMap(incoming_operation_types_, IncomingOperationType_MAX + 1); + InitializeMap(listener_event_types_, ListenerEventType_MAX + 1); + InitializeMap(client_error_types_, ClientErrorType_MAX + 1); +} + +void Statistics::GetNonZeroStatistics( + vector<pair<string, int> >* performance_counters) { + // Add the non-zero values from the different maps to performance_counters. + FillWithNonZeroStatistics( + sent_message_types_, SentMessageType_MAX + 1, SentMessageType_names, + "SentMessageType.", performance_counters); + FillWithNonZeroStatistics( + received_message_types_, ReceivedMessageType_MAX + 1, + ReceivedMessageType_names, "ReceivedMessageType.", + performance_counters); + FillWithNonZeroStatistics( + incoming_operation_types_, IncomingOperationType_MAX + 1, + IncomingOperationType_names, "IncomingOperationType.", + performance_counters); + FillWithNonZeroStatistics( + listener_event_types_, ListenerEventType_MAX + 1, ListenerEventType_names, + "ListenerEventType.", performance_counters); + FillWithNonZeroStatistics( + client_error_types_, ClientErrorType_MAX + 1, ClientErrorType_names, + "ClientErrorType.", performance_counters); +} + +/* Modifies result to contain those statistics from map whose value is > 0. */ +void Statistics::FillWithNonZeroStatistics( + int map[], int size, const char* names[], const char* prefix, + vector<pair<string, int> >* destination) { + for (int i = 0; i < size; ++i) { + if (map[i] > 0) { + destination->push_back( + make_pair(StringPrintf("%s%s", prefix, names[i]), map[i])); + } + } +} + +void Statistics::InitializeMap(int map[], int size) { + for (int i = 0; i < size; ++i) { + map[i] = 0; + } +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/statistics.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/statistics.h new file mode 100644 index 0000000..fcbc49e --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/statistics.h @@ -0,0 +1,234 @@ +// Copyright 2012 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. + +// Statistics for the Ticl, e.g., number of registration calls, number of token +// mismatches, etc. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_STATISTICS_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_STATISTICS_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "google/cacheinvalidation/deps/stl-namespace.h" +#include "google/cacheinvalidation/deps/string_util.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::pair; +using INVALIDATION_STL_NAMESPACE::string; +using INVALIDATION_STL_NAMESPACE::vector; + +class Statistics { + public: + // Implementation: To classify the statistics a bit better, we have a few + // enums to track different different types of statistics, e.g., sent message + // types, errors, etc. For each statistic type, we create a map and provide a + // method to record an event for each type of statistic. + + /* Types of messages sent to the server: ClientToServerMessage for their + * description. + */ + enum SentMessageType { + SentMessageType_INFO, + SentMessageType_INITIALIZE, + SentMessageType_INVALIDATION_ACK, + SentMessageType_REGISTRATION, + SentMessageType_REGISTRATION_SYNC, + SentMessageType_TOTAL, // Refers to the actual ClientToServerMessage + // message sent on the network. + }; + static const SentMessageType SentMessageType_MIN = SentMessageType_INFO; + static const SentMessageType SentMessageType_MAX = SentMessageType_TOTAL; + static const char* SentMessageType_names[]; + + /* Types of messages received from the server: ServerToClientMessage for their + * description. + */ + enum ReceivedMessageType { + ReceivedMessageType_INFO_REQUEST, + ReceivedMessageType_INVALIDATION, + ReceivedMessageType_REGISTRATION_STATUS, + ReceivedMessageType_REGISTRATION_SYNC_REQUEST, + ReceivedMessageType_TOKEN_CONTROL, + ReceivedMessageType_ERROR, + ReceivedMessageType_CONFIG_CHANGE, + ReceivedMessageType_TOTAL, // Refers to the actual ServerToClientMessage + // messages received from the network. + }; + static const ReceivedMessageType ReceivedMessageType_MIN = + ReceivedMessageType_INFO_REQUEST; + static const ReceivedMessageType ReceivedMessageType_MAX = + ReceivedMessageType_TOTAL; + static const char* ReceivedMessageType_names[]; + + /* Interesting API calls coming from the application (see InvalidationClient). + */ + enum IncomingOperationType { + IncomingOperationType_ACKNOWLEDGE, + IncomingOperationType_REGISTRATION, + IncomingOperationType_UNREGISTRATION, + }; + static const IncomingOperationType IncomingOperationType_MIN = + IncomingOperationType_ACKNOWLEDGE; + static const IncomingOperationType IncomingOperationType_MAX = + IncomingOperationType_UNREGISTRATION; + static const char* IncomingOperationType_names[]; + + /* Different types of events issued by the InvalidationListener. */ + enum ListenerEventType { + ListenerEventType_INFORM_ERROR, + ListenerEventType_INFORM_REGISTRATION_FAILURE, + ListenerEventType_INFORM_REGISTRATION_STATUS, + ListenerEventType_INVALIDATE, + ListenerEventType_INVALIDATE_ALL, + ListenerEventType_INVALIDATE_UNKNOWN, + ListenerEventType_REISSUE_REGISTRATIONS, + }; + static const ListenerEventType ListenerEventType_MIN = + ListenerEventType_INFORM_ERROR; + static const ListenerEventType ListenerEventType_MAX = + ListenerEventType_REISSUE_REGISTRATIONS; + static const char* ListenerEventType_names[]; + + /* Different types of errors observed by the Ticl. */ + enum ClientErrorType { + /* Acknowledge call received from client with a bad handle. */ + ClientErrorType_ACKNOWLEDGE_HANDLE_FAILURE, + + /* Incoming message dropped due to parsing, validation problems. */ + ClientErrorType_INCOMING_MESSAGE_FAILURE, + + /* Tried to send an outgoing message that was invalid. */ + ClientErrorType_OUTGOING_MESSAGE_FAILURE, + + /* Persistent state failed to deserialize correctly. */ + ClientErrorType_PERSISTENT_DESERIALIZATION_FAILURE, + + /* Read of blob from persistent state failed. */ + ClientErrorType_PERSISTENT_READ_FAILURE, + + /* Write of blob from persistent state failed. */ + ClientErrorType_PERSISTENT_WRITE_FAILURE, + + /* Message received with incompatible protocol version. */ + ClientErrorType_PROTOCOL_VERSION_FAILURE, + + /* Registration at client and server is different, e.g., client thinks it is + * registered while the server says it is unregistered (of course, sync will + * fix it). + */ + ClientErrorType_REGISTRATION_DISCREPANCY, + + /* The nonce from the server did not match the current nonce by the client. + */ + ClientErrorType_NONCE_MISMATCH, + + /* The current token at the client is different from the token in the + * incoming message. + */ + ClientErrorType_TOKEN_MISMATCH, + + /* No message sent due to token missing. */ + ClientErrorType_TOKEN_MISSING_FAILURE, + + /* Received a message with a token (transient) failure. */ + ClientErrorType_TOKEN_TRANSIENT_FAILURE, + }; + static const ClientErrorType ClientErrorType_MIN = + ClientErrorType_ACKNOWLEDGE_HANDLE_FAILURE; + static const ClientErrorType ClientErrorType_MAX = + ClientErrorType_TOKEN_TRANSIENT_FAILURE; + static const char* ClientErrorType_names[]; + + // Arrays for each type of Statistic to keep track of how many times each + // event has occurred. + + Statistics(); + + /* Returns the counter value for client_error_type. */ + int GetClientErrorCounterForTest(ClientErrorType client_error_type) { + return client_error_types_[client_error_type]; + } + + /* Returns the counter value for sent_message_type. */ + int GetSentMessageCounterForTest(SentMessageType sent_message_type) { + return sent_message_types_[sent_message_type]; + } + + /* Returns the counter value for received_message_type. */ + int GetReceivedMessageCounterForTest( + ReceivedMessageType received_message_type) { + return received_message_types_[received_message_type]; + } + + /* Records the fact that a message of type sent_message_type has been sent. */ + void RecordSentMessage(SentMessageType sent_message_type) { + ++sent_message_types_[sent_message_type]; + } + + /* Records the fact that a message of type received_message_type has been + * received. + */ + void RecordReceivedMessage(ReceivedMessageType received_message_type) { + ++received_message_types_[received_message_type]; + } + + /* Records the fact that the application has made a call of type + * incoming_operation_type. + */ + void RecordIncomingOperation(IncomingOperationType incoming_operation_type) { + ++incoming_operation_types_[incoming_operation_type]; + } + + /* Records the fact that the listener has issued an event of type + * listener_event_type. + */ + void RecordListenerEvent(ListenerEventType listener_event_type) { + ++listener_event_types_[listener_event_type]; + } + + /* Records the fact that the client has observed an error of type + * client_error_type. + */ + void RecordError(ClientErrorType client_error_type) { + ++client_error_types_[client_error_type]; + } + + /* Modifies performance_counters to contain all the statistics that are + * non-zero. Each pair has the name of the statistic event and the number of + * times that event has occurred since the client started. + */ + void GetNonZeroStatistics(vector<pair<string, int> >* performance_counters); + + /* Modifies result to contain those statistics from map whose value is > 0. */ + static void FillWithNonZeroStatistics( + int map[], int size, const char* names[], const char* prefix, + vector<pair<string, int> >* destination); + + /* Initialzes all values for keys in map to be 0. */ + static void InitializeMap(int map[], int size); + + private: + int sent_message_types_[SentMessageType_MAX + 1]; + int received_message_types_[ReceivedMessageType_MAX + 1]; + int incoming_operation_types_[IncomingOperationType_MAX + 1]; + int listener_event_types_[ListenerEventType_MAX + 1]; + int client_error_types_[ClientErrorType_MAX + 1]; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_STATISTICS_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle.cc new file mode 100644 index 0000000..fa84fc8 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle.cc @@ -0,0 +1,113 @@ +// Copyright 2012 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. + +// Throttles calls to a function. + +#include "google/cacheinvalidation/impl/throttle.h" + +#include <algorithm> + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/callback.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::max; + +Throttle::Throttle( + const RepeatedPtrField<RateLimitP>& rate_limits, Scheduler* scheduler, + Closure* listener) + : rate_limits_(rate_limits), scheduler_(scheduler), listener_(listener), + timer_scheduled_(false) { + + // Find the largest 'count' in all of the rate limits, as this is the size of + // the buffer of recent messages we need to retain. + max_recent_events_ = 1; + for (size_t i = 0; i < static_cast<size_t>(rate_limits_.size()); ++i) { + const RateLimitP& rate_limit = rate_limits.Get(i); + CHECK(rate_limit.window_ms() > rate_limit.count()) << + "Windows size too small"; + max_recent_events_ = max(static_cast<int>(max_recent_events_), + rate_limits_.Get(i).count()); + } +} + +void Throttle::Fire() { + if (timer_scheduled_) { + // We're already rate-limited and have a deferred call scheduled. Just + // return. The flag will be reset when the deferred task runs. + return; + } + // Go through all of the limits to see if we've hit one. If so, schedule a + // task to try again once that limit won't be violated. If no limits would be + // violated, send. + Time now = scheduler_->GetCurrentTime(); + for (size_t i = 0; i < static_cast<size_t>(rate_limits_.size()); ++i) { + RateLimitP rate_limit = rate_limits_.Get(i); + + // We're now checking whether sending would violate a rate limit of 'count' + // messages per 'window_size'. + int count = rate_limit.count(); + TimeDelta window_size = TimeDelta::FromMilliseconds(rate_limit.window_ms()); + + // First, see how many messages we've sent so far (up to the size of our + // recent message buffer). + int num_recent_messages = recent_event_times_.size(); + + // Check whether we've sent enough messages yet that we even need to + // consider this rate limit. + if (num_recent_messages >= count) { + // If we've sent enough messages to reach this limit, see how long ago we + // sent the first message in the interval, and add sufficient delay to + // avoid violating the rate limit. + + // We have sent at least 'count' messages. See how long ago we sent the + // 'count'-th last message. This defines the start of a window in which + // no more than 'count' messages may be sent. + Time window_start = recent_event_times_[num_recent_messages - count]; + + // The end of this window is 'window_size' after the start. + Time window_end = window_start + window_size; + + // Check where the end of the window is relative to the current time. If + // the end of the window is in the future, then sending now would violate + // the rate limit, so we must defer. + TimeDelta window_end_from_now = window_end - now; + if (window_end_from_now > TimeDelta::FromSeconds(0)) { + // Rate limit would be violated, so schedule a task to try again. + + // Set the flag to indicate we have a deferred task scheduled. No need + // to continue checking other rate limits now. + timer_scheduled_ = true; + scheduler_->Schedule( + window_end_from_now, + NewPermanentCallback(this, &Throttle::RetryFire)); + return; + } + } + } + // We checked all the rate limits, and none would have been violated, so it's + // safe to call the listener. + listener_->Run(); + + // Record the fact that we're triggering an event now. + recent_event_times_.push_back(scheduler_->GetCurrentTime()); + + // Only save up to max_recent_events_ event times. + if (recent_event_times_.size() > max_recent_events_) { + recent_event_times_.pop_front(); + } +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle.h new file mode 100644 index 0000000..448dcf8 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle.h @@ -0,0 +1,88 @@ +// Copyright 2012 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. + +// Throttles calls to a function. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_THROTTLE_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_THROTTLE_H_ + +#include <cstddef> +#include <deque> +#include <vector> + +#include "google/cacheinvalidation/deps/callback.h" +#include "google/cacheinvalidation/deps/logging.h" +#include "google/cacheinvalidation/deps/scoped_ptr.h" +#include "google/cacheinvalidation/deps/stl-namespace.h" +#include "google/cacheinvalidation/deps/time.h" +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" + +namespace invalidation { + +class Scheduler; + +using INVALIDATION_STL_NAMESPACE::deque; +using INVALIDATION_STL_NAMESPACE::vector; + +// Provides an abstraction for multi-level rate-limiting. For example, the +// default limits state that no more than one message should be sent per second, +// or six per minute. Rate-limiting is implemented by maintaining a buffer of +// recent messages, which is as large as the highest 'count' property. Note: +// this means the object consumes space proportional to the _largest_ 'count'. +class Throttle { + public: + // Constructs a throttler to enforce the given rate limits for the given + // listener, using the given system resources. Ownership of scheduler is + // retained by the caller, but the throttle takes ownership of the listener. + Throttle(const RepeatedPtrField<RateLimitP>& rate_limits, + Scheduler* scheduler, Closure* listener); + + // If calling the listener would not violate the rate limits, does so. + // Otherwise, schedules a timer to do so as soon as doing so would not violate + // the rate limits, unless such a timer is already set, in which case does + // nothing. I.e., once the rate limit is reached, additional calls are not + // queued. + void Fire(); + + private: + // Retries a call to Fire() after some delay. + void RetryFire() { + timer_scheduled_ = false; + Fire(); + } + + // Rate limits to be enforced by this object. + RepeatedPtrField<RateLimitP> rate_limits_; + + // Scheduler for reading the current time and scheduling tasks that need to be + // delayed. + Scheduler* scheduler_; + + // The closure whose calls are throttled. + scoped_ptr<Closure> listener_; + + // Whether we've already scheduled a deferred call. + bool timer_scheduled_; + + // A buffer of recent events, so we can determine the length of the interval + // in which we made the most recent K events. + deque<Time> recent_event_times_; + + // The maximum size of the recent_event_times_ buffer. + size_t max_recent_events_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_THROTTLE_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle_test.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle_test.cc new file mode 100644 index 0000000..2df0de0 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle_test.cc @@ -0,0 +1,191 @@ +// Copyright 2012 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. + +// Tests the throttle. + +#include "google/cacheinvalidation/deps/googletest.h" +#include "google/cacheinvalidation/impl/proto-helpers.h" +#include "google/cacheinvalidation/impl/throttle.h" +#include "google/cacheinvalidation/test/deterministic-scheduler.h" +#include "google/cacheinvalidation/test/test-logger.h" + +namespace invalidation { + +class ThrottleTest : public testing::Test { + public: + ThrottleTest() : call_count_(0) {} + + virtual ~ThrottleTest() {} + + // Increments the call count. + void IncrementCounter() { + ++call_count_; + } + + // Increments the call count and checks state to ensure that rate limits are + // being observed. + void IncrementAndCheckRateLimits() { + // Increment the call count. + ++call_count_; + // Check that we haven't been called within the last one second. + Time now = scheduler_->GetCurrentTime(); + ASSERT_TRUE((now - last_call_time_) >= TimeDelta::FromSeconds(1)); + // Update the last time we were called to now. + last_call_time_ = now; + // Check that enough time has passed to allow the number of calls we've + // received. + Time min_time = start_time_ + TimeDelta::FromMinutes( + (call_count_ - 1) / kMessagesPerMinute); + ASSERT_TRUE(min_time <= now); + } + + void SetUp() { + logger_.reset(new TestLogger()); + scheduler_.reset(new DeterministicScheduler(logger_.get())); + start_time_ = scheduler_->GetCurrentTime(); + call_count_ = 0; + last_call_time_ = Time() - TimeDelta::FromHours(1); + ProtoHelpers::InitRateLimitP(1000, kMessagesPerSecond, rate_limits_.Add()); + ProtoHelpers::InitRateLimitP(60 * 1000, kMessagesPerMinute, + rate_limits_.Add()); + } + + int call_count_; + Time start_time_; + Time last_call_time_; + scoped_ptr<DeterministicScheduler> scheduler_; + scoped_ptr<Logger> logger_; + RepeatedPtrField<RateLimitP> rate_limits_; + + static const int kMessagesPerSecond; + static const int kMessagesPerMinute; +}; + +const int ThrottleTest::kMessagesPerSecond = 1; +const int ThrottleTest::kMessagesPerMinute = 6; + +/* Make a throttler similar to what we expect the Ticl to use and check that it + * behaves as expected when called at a number of specific times. More + * specifically: + * + * 1. Check that the first call to Fire() triggers a call immediately. + * 2. Subsequent calls within the next one second don't trigger any calls. + * 3. After one second, one (and only one) buffered call is triggered. + * 4. If we Fire() slowly, each will trigger an immediate call until we reach + * the per-minute rate limit. + * 5. However, after a minute, another call i. + */ +TEST_F(ThrottleTest, ThrottlingScripted) { + scheduler_->StartScheduler(); + Closure* listener = + NewPermanentCallback(this, &ThrottleTest::IncrementCounter); + + scoped_ptr<Throttle> throttle( + new Throttle(rate_limits_, scheduler_.get(), listener)); + + // The first time we fire(), it should call right away. + throttle->Fire(); + scheduler_->PassTime(TimeDelta()); + ASSERT_EQ(1, call_count_); + + // However, if we now fire() a bunch more times within one second, there + // should be no more calls to the listener ... + TimeDelta short_interval = TimeDelta::FromMilliseconds(80); + int fire_count = 10; + ASSERT_TRUE(short_interval * fire_count < TimeDelta::FromSeconds(1)); + for (int i = 0; i < fire_count; ++i) { + scheduler_->PassTime(short_interval); + throttle->Fire(); + ASSERT_EQ(1, call_count_); + } + + // Time since first event is now fireCount * intervalBetweenFires, i.e., 800. + + // ... until the short throttle interval passes, at which time it should be + // called once more. + scheduler_->PassTime( + start_time_ + TimeDelta::FromSeconds(1) - scheduler_->GetCurrentTime()); + + ASSERT_EQ(2, call_count_); + + // However, the prior fire() calls don't get queued up, so no more calls to + // the listener will occur unless we fire() again. + scheduler_->PassTime(TimeDelta::FromSeconds(2)); + ASSERT_EQ(2, call_count_); + + // At this point, we've fired twice within a few seconds. We can fire + // (kMessagesPerMinute - 2) more times within a minute until we get + // throttled. + TimeDelta long_interval = TimeDelta::FromSeconds(3); + for (int i = 0; i < kMessagesPerMinute - 2; ++i) { + throttle->Fire(); + ASSERT_EQ(3 + i, call_count_); + scheduler_->PassTime(long_interval); + ASSERT_EQ(3 + i, call_count_); + } + + // Now we've sent kMessagesPerMinute times. If we fire again, nothing should + // happen. + throttle->Fire(); + scheduler_->PassTime(TimeDelta()); + ASSERT_EQ(kMessagesPerMinute, call_count_); + + // Now if we fire slowly, we still shouldn't make calls, since we'd violate + // the larger rate limit interval. + int fire_attempts = + ((start_time_ + TimeDelta::FromMinutes(1) - scheduler_->GetCurrentTime()) + / long_interval) - 1; + // This value should be 20. + for (int i = 0; i < fire_attempts; ++i) { + scheduler_->PassTime(long_interval); + throttle->Fire(); + ASSERT_EQ(kMessagesPerMinute, call_count_); + } + + Time time_to_send_again = start_time_ + TimeDelta::FromMinutes(1); + scheduler_->PassTime(time_to_send_again - scheduler_->GetCurrentTime()); + + ASSERT_EQ(kMessagesPerMinute + 1, call_count_); +} + +/* Test that if we keep calling fire() every millisecond, we never violate the + * rate limits, and the expected number of total events is allowed through. + */ +TEST_F(ThrottleTest, ThrottlingStorm) { + scheduler_->StartScheduler(); + Closure* listener = + NewPermanentCallback(this, &ThrottleTest::IncrementAndCheckRateLimits); + + // Throttler allowing one call per second and six per minute. + scoped_ptr<Throttle> throttle( + new Throttle(rate_limits_, scheduler_.get(), listener)); + + // For five minutes, call Fire() every ten milliseconds, and make sure the + // rate limits are respected. + TimeDelta fine_interval = TimeDelta::FromMilliseconds(10); + int duration_minutes = 5; + TimeDelta duration = TimeDelta::FromMinutes(duration_minutes); + int num_iterations = duration / fine_interval; + for (int i = 0; i < num_iterations; ++i) { + throttle->Fire(); + scheduler_->PassTime(fine_interval); + } + + // Expect kMessagesPerMinute to be sent per minute for duration_minutes, plus + // one extra because we end on the precise boundary at which the next message + // is allowed to be sent. + ASSERT_EQ((kMessagesPerMinute * duration_minutes) + 1, call_count_); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/ticl-message-validator.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/ticl-message-validator.cc new file mode 100644 index 0000000..9a602cb --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/ticl-message-validator.cc @@ -0,0 +1,369 @@ +// Copyright 2012 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. + +// Validator for v2 protocol messages. + +#include "google/cacheinvalidation/impl/ticl-message-validator.h" + +#include "google/cacheinvalidation/impl/log-macro.h" +#include "google/cacheinvalidation/impl/proto-helpers.h" +#include "google/cacheinvalidation/include/system-resources.h" + +namespace invalidation { + +// High-level design: validation works via the collaboration of a set of macros +// and template method specializations that obey a specific protocol. A +// validator for a particular type is defined by a specialization of the method: +// +// template<typename T> +// void TiclMessageValidator::Validate(const T& message, bool* result); +// +// A macro, DEFINE_VALIDATOR(type) is defined below to help prevent mistakes in +// these definitions and to improve code readability. For example, to define +// the validator for the type ObjectIdP, we'd write: +// +// DEFINE_VALIDATOR(ObjectIdP) { /* validation constraints ... */ } +// +// The choice of the names |message| and |result| is significant, as many of the +// macros assume that these refer respectively to the message being validated +// and the address in which the validation result is to be stored. +// +// When a validator is called, |*result| is initially |true|. To reject the +// message, the validator sets |*result| to |false| and returns. Otherwise, it +// simply allows control flow to continue; if no reason is found to reject the +// message, control eventually returns to the caller with |*result| still set to +// |true|, indicating that the message is acceptable. This protocol keeps the +// bodies of the validation methods clean--otherwise they would all need need to +// end with explicit |return| statements. +// +// A validator typically consists of a collection of constraints, at least one +// per field in the message. Several macros are defined for common constraints, +// including: +// +// REQUIRE(field): requires that (optional) |field| be present and valid. +// ALLOW(field): allows (optional) |field| if valid. +// ZERO_OR_MORE(field): validates each element of the (repeated) |field|. +// ONE_OR_MORE(field): like ZERO_OR_MORE, but requires at least one element. +// NON_EMPTY(field): checks that the string |field| is non-empty (if present). +// NON_NEGATIVE(field): checks that the integral |field| is >= 0 (if present). +// +// For custom constraints, the CONDITION(expr) macro allows an arbitrary boolean +// expression, which will generally refer to |message|. +// +// Note that REQUIRE, ALLOW, ZERO_OR_MORE, and ONE_OR_MORE all perform recursive +// validation of the mentioned fields. A validation method must therefore be +// defined for the type of the field, or there will be a link-time error. + + +// Macros: + +// Macro to define a specialization of the |Validate| method for the given +// |type|. This must be followed by a method body in curly braces defining +// constraints on |message|, which is bound to a value of the given type. If +// |message| is valid, no action is necessary; if invalid, a diagnostic message +// should be logged via |logger_|, and |*result| should be set to false. +#define DEFINE_VALIDATOR(type) \ + template<> \ + void TiclMessageValidator::Validate(const type& message, bool* result) + +// Expands into a conditional that checks whether |field| is present in +// |message| and valid. +#define REQUIRE(field) \ + if (!message.has_##field()) { \ + TLOG(logger_, SEVERE, "required field " #field " missing from %s", \ + ProtoHelpers::ToString(message).c_str()); \ + *result = false; \ + return; \ + } \ + ALLOW(field); + +// Expands into a conditional that checks whether |field| is present in +// |message|. If so, validates |message.field()|; otherwise, does nothing. +#define ALLOW(field) \ + if (message.has_##field()) { \ + Validate(message.field(), result); \ + if (!*result) { \ + TLOG(logger_, SEVERE, "field " #field " failed validation in %s", \ + ProtoHelpers::ToString(message).c_str()); \ + return; \ + } \ + } + +// Expands into a conditional that checks that, if |field| is present in +// |message|, then it is greater than or equal to |value|. +#define GREATER_OR_EQUAL(field, value) \ + if (message.has_##field() && (message.field() < value)) { \ + TLOG(logger_, SEVERE, \ + #field " must be greater than or equal to %d; was %d", \ + value, message.field()); \ + *result = false; \ + return; \ + } + +// Expands into a conditional that checks that, if the specified numeric |field| +// is present, that it is non-negative. +#define NON_NEGATIVE(field) GREATER_OR_EQUAL(field, 0) + +// Expands into a conditional that checks that, if the specified string |field| +// is present, that it is non-empty. +#define NON_EMPTY(field) \ + if (message.has_##field() && message.field().empty()) { \ + TLOG(logger_, SEVERE, #field " must be non-empty"); \ + *result = false; \ + return; \ + } + +// Expands into a loop that checks that all elements of the repeated |field| are +// valid. +#define ZERO_OR_MORE(field) \ + for (int i = 0; i < message.field##_size(); ++i) { \ + Validate(message.field(i), result); \ + if (!*result) { \ + TLOG(logger_, SEVERE, "field " #field " #%d failed validation in %s", \ + i, ProtoHelpers::ToString(message).c_str()); \ + *result = false; \ + return; \ + } \ + } + +// Expands into a loop that checks that there is at least one element of the +// repeated |field|, and that all are valid. +#define ONE_OR_MORE(field) \ + if (message.field##_size() == 0) { \ + TLOG(logger_, SEVERE, "at least one " #field " required in %s", \ + ProtoHelpers::ToString(message).c_str()); \ + *result = false; \ + return; \ + } \ + ZERO_OR_MORE(field) + +// Expands into code that checks that the arbitrary condition |expr| is true. +#define CONDITION(expr) \ + *result = expr; \ + if (!*result) { \ + TLOG(logger_, SEVERE, #expr " not satisfied by %s", \ + ProtoHelpers::ToString(message).c_str()); \ + return; \ + } + + +// Validators: + +// No constraints on primitive types by default. +DEFINE_VALIDATOR(bool) {} +DEFINE_VALIDATOR(int) {} +DEFINE_VALIDATOR(int64) {} +DEFINE_VALIDATOR(string) {} + +// Similarly, for now enum values are always considered valid. +DEFINE_VALIDATOR(ErrorMessage::Code) {} +DEFINE_VALIDATOR(InfoRequestMessage::InfoType) {} +DEFINE_VALIDATOR(InitializeMessage::DigestSerializationType) {} +DEFINE_VALIDATOR(RegistrationP::OpType) {} +DEFINE_VALIDATOR(StatusP::Code) {} + +DEFINE_VALIDATOR(Version) { + REQUIRE(major_version); + NON_NEGATIVE(major_version); + REQUIRE(minor_version); + NON_NEGATIVE(minor_version); +} + +DEFINE_VALIDATOR(ProtocolVersion) { + REQUIRE(version); +} + +DEFINE_VALIDATOR(ObjectIdP) { + REQUIRE(name); + REQUIRE(source); + NON_NEGATIVE(source); +} + +DEFINE_VALIDATOR(InvalidationP) { + REQUIRE(object_id); + REQUIRE(is_known_version); + REQUIRE(version); + NON_NEGATIVE(version); + ALLOW(payload); +} + +DEFINE_VALIDATOR(RegistrationP) { + REQUIRE(object_id); + REQUIRE(op_type); +} + +DEFINE_VALIDATOR(RegistrationSummary) { + REQUIRE(num_registrations); + NON_NEGATIVE(num_registrations); + REQUIRE(registration_digest); + NON_EMPTY(registration_digest); +} + +DEFINE_VALIDATOR(InvalidationMessage) { + ONE_OR_MORE(invalidation); +} + +DEFINE_VALIDATOR(ClientHeader) { + REQUIRE(protocol_version); + ALLOW(client_token); + NON_EMPTY(client_token); + ALLOW(registration_summary); + REQUIRE(client_time_ms); + REQUIRE(max_known_server_time_ms); + ALLOW(message_id); + ALLOW(client_type); +} + +DEFINE_VALIDATOR(ApplicationClientIdP) { + REQUIRE(client_type); + REQUIRE(client_name); + NON_EMPTY(client_name); +} + +DEFINE_VALIDATOR(InitializeMessage) { + REQUIRE(client_type); + REQUIRE(nonce); + NON_EMPTY(nonce); + REQUIRE(digest_serialization_type); + REQUIRE(application_client_id); +} + +DEFINE_VALIDATOR(RegistrationMessage) { + ONE_OR_MORE(registration); +} + +DEFINE_VALIDATOR(ClientVersion) { + REQUIRE(version); + REQUIRE(platform); + REQUIRE(language); + REQUIRE(application_info); +} + +DEFINE_VALIDATOR(PropertyRecord) { + REQUIRE(name); + REQUIRE(value); +} + +DEFINE_VALIDATOR(RateLimitP) { + REQUIRE(window_ms); + GREATER_OR_EQUAL(window_ms, 1000); + CONDITION(message.window_ms() > message.count()); + REQUIRE(count); +} + +DEFINE_VALIDATOR(ProtocolHandlerConfigP) { + ALLOW(batching_delay_ms); + ZERO_OR_MORE(rate_limit); +} + +DEFINE_VALIDATOR(ClientConfigP) { + REQUIRE(version); + ALLOW(network_timeout_delay_ms); + ALLOW(write_retry_delay_ms); + ALLOW(heartbeat_interval_ms); + ALLOW(perf_counter_delay_ms); + ALLOW(max_exponential_backoff_factor); + ALLOW(smear_percent); + ALLOW(is_transient); + ALLOW(initial_persistent_heartbeat_delay_ms); + ALLOW(channel_supports_offline_delivery); + REQUIRE(protocol_handler_config); + ALLOW(offline_heartbeat_threshold_ms); + ALLOW(allow_suppression); +} + +DEFINE_VALIDATOR(InfoMessage) { + REQUIRE(client_version); + ZERO_OR_MORE(config_parameter); + ZERO_OR_MORE(performance_counter); + ALLOW(client_config); + ALLOW(server_registration_summary_requested); +} + +DEFINE_VALIDATOR(RegistrationSubtree) { + ZERO_OR_MORE(registered_object); +} + +DEFINE_VALIDATOR(RegistrationSyncMessage) { + ONE_OR_MORE(subtree); +} + +DEFINE_VALIDATOR(ClientToServerMessage) { + REQUIRE(header); + ALLOW(info_message); + ALLOW(initialize_message); + ALLOW(invalidation_ack_message); + ALLOW(registration_message); + ALLOW(registration_sync_message); + CONDITION(message.has_initialize_message() ^ + message.header().has_client_token()); +} + +DEFINE_VALIDATOR(ServerHeader) { + REQUIRE(protocol_version); + REQUIRE(client_token); + NON_EMPTY(client_token); + ALLOW(registration_summary); + REQUIRE(server_time_ms); + NON_NEGATIVE(server_time_ms); + ALLOW(message_id); + NON_EMPTY(message_id); +} + +DEFINE_VALIDATOR(StatusP) { + REQUIRE(code); + ALLOW(description); +} + +DEFINE_VALIDATOR(TokenControlMessage) { + ALLOW(new_token); +} + +DEFINE_VALIDATOR(ErrorMessage) { + REQUIRE(code); + REQUIRE(description); +} + +DEFINE_VALIDATOR(RegistrationStatus) { + REQUIRE(registration); + REQUIRE(status); +} + +DEFINE_VALIDATOR(RegistrationStatusMessage) { + ONE_OR_MORE(registration_status); +} + +DEFINE_VALIDATOR(RegistrationSyncRequestMessage) {} + +DEFINE_VALIDATOR(InfoRequestMessage) { + ONE_OR_MORE(info_type); +} + +DEFINE_VALIDATOR(ConfigChangeMessage) { + ALLOW(next_message_delay_ms); + GREATER_OR_EQUAL(next_message_delay_ms, 1); +} + +DEFINE_VALIDATOR(ServerToClientMessage) { + REQUIRE(header); + ALLOW(token_control_message); + ALLOW(invalidation_message); + ALLOW(registration_status_message); + ALLOW(registration_sync_request_message); + ALLOW(config_change_message); + ALLOW(info_request_message); + ALLOW(error_message); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/ticl-message-validator.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/ticl-message-validator.h new file mode 100644 index 0000000..ce0afcb --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/impl/ticl-message-validator.h @@ -0,0 +1,55 @@ +// Copyright 2012 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. + +// Validator for v2 protocol messages. + +#ifndef GOOGLE_CACHEINVALIDATION_IMPL_TICL_MESSAGE_VALIDATOR_H_ +#define GOOGLE_CACHEINVALIDATION_IMPL_TICL_MESSAGE_VALIDATOR_H_ + +#include "google/cacheinvalidation/impl/client-protocol-namespace-fix.h" + +namespace invalidation { + +class Logger; + +class TiclMessageValidator { + public: + TiclMessageValidator(Logger* logger) : logger_(logger) {} + + // Generic IsValid() method. Delegates to the private |Validate| helper + // method. + template<typename T> + bool IsValid(const T& message) { + bool result = true; + Validate(message, &result); + return result; + } + + private: + // Validates a message. For each type of message to be validated, there + // should be a specialization of this method. Instead of returning a boolean, + // the method stores |false| in |*result| if the message is invalid. Thus, + // the caller must initialize |*result| to |true|. Following this pattern + // allows the specific validation methods to be simpler (i.e., a method that + // accepts all messages has an empty body instead of having to return |true|). + template<typename T> + void Validate(const T& message, bool* result); + + private: + Logger* logger_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_IMPL_TICL_MESSAGE_VALIDATOR_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-client-factory.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-client-factory.h new file mode 100644 index 0000000..4b92495 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-client-factory.h @@ -0,0 +1,162 @@ +// Copyright 2012 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. + +// +// Factory for the invalidation client library. + +#ifndef GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_CLIENT_FACTORY_H_ +#define GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_CLIENT_FACTORY_H_ + +#include <string> + +#include "google/cacheinvalidation/include/types.h" +#include "google/cacheinvalidation/include/invalidation-listener.h" +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/stl-namespace.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::string; + +/* Application-provided configuration for an invalidation client. */ +class InvalidationClientConfig { + public: + /* Constructs an InvalidationClientConfig instance. + * + * Arguments: + * client_type Client type code as assigned by the notification system's + * backend. + * client_name Id/name of the client in the application's own naming + * scheme. + * application_name Name of the application using the library (for + * debugging/monitoring) + * allow_suppression If false, invalidateUnknownVersion() is called + * whenever suppression occurs. + */ + InvalidationClientConfig(int client_type, + const string& client_name, + const string& application_name, + bool allow_suppression) : + client_type_(client_type), client_name_(client_name), + application_name_(application_name), + allow_suppression_(allow_suppression) { + } + + int32 client_type() const { + return client_type_; + } + + const string& client_name() const { + return client_name_; + } + + const string& application_name() const { + return application_name_; + } + + bool allow_suppression() const { + return allow_suppression_; + } + + private: + const int32 client_type_; + const string client_name_; + const string application_name_; + const bool allow_suppression_; +}; + +// A class for new factory methods. These methods will be static, so this class +// is essentially just a namespace. This is more consistent with how the +// factory works in other languages, and it avoids overload issues with the old +// methods defined below. +class ClientFactory { + public: + /* Constructs an invalidation client library instance with a default + * configuration. Caller owns returned space. + * + * Arguments: + * resources SystemResources to use for logging, scheduling, persistence, + * and network connectivity + * config configuration provided by the application + * listener callback object for invalidation events + */ + static InvalidationClient* Create( + SystemResources* resources, + const InvalidationClientConfig& config, + InvalidationListener* listener); + + /* Constructs an invalidation client library instance with a configuration + * initialized for testing. Caller owns returned space. + * + * Arguments: + * resources SystemResources to use for logging, scheduling, persistence, + * and network connectivity + * client_type client type code as assigned by the notification system's + * backend + * client_name id/name of the client in the application's own naming scheme + * application_name name of the application using the library (for + * debugging/monitoring) + * listener callback object for invalidation events + */ + static InvalidationClient* CreateForTest( + SystemResources* resources, + const InvalidationClientConfig& config, + InvalidationListener* listener); +}; + +/* Constructs an invalidation client library instance with a default + * configuration. Deprecated, please use the version which takes an + * InvalidationClientConfig. Caller owns returned space. + * + * Arguments: + * resources SystemResources to use for logging, scheduling, persistence, + * and network connectivity + * client_type client type code as assigned by the notification system's + * backend + * client_name id/name of the client in the application's own naming scheme + * application_name name of the application using the library (for + * debugging/monitoring) + * listener callback object for invalidation events + */ +InvalidationClient* CreateInvalidationClient( + SystemResources* resources, + int client_type, + const string& client_name, + const string& application_name, + InvalidationListener* listener); + +/* Constructs an invalidation client library instance with a configuration + * initialized for testing. Deprecated, please use the version which takes an + * InvalidationClientConfig. Caller owns returned space. + * + * Arguments: + * resources SystemResources to use for logging, scheduling, persistence, + * and network connectivity + * client_type client type code as assigned by the notification system's + * backend + * client_name id/name of the client in the application's own naming scheme + * application_name name of the application using the library (for + * debugging/monitoring) + * listener callback object for invalidation events + */ +InvalidationClient* CreateInvalidationClientForTest( + SystemResources* resources, + int client_type, + const string& client_name, + const string& application_name, + InvalidationListener* listener); + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_CLIENT_FACTORY_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-client.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-client.h new file mode 100644 index 0000000..ec4d955 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-client.h @@ -0,0 +1,116 @@ +// Copyright 2012 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. + +// Interface for the invalidation client library. + +#ifndef GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_CLIENT_H_ +#define GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_CLIENT_H_ + +#include <vector> + +#include "google/cacheinvalidation/deps/stl-namespace.h" + +namespace invalidation { + +using ::INVALIDATION_STL_NAMESPACE::vector; + +class AckHandle; +class Invalidation; +class ObjectId; + +class InvalidationClient { + public: + virtual ~InvalidationClient() {} + + /* Starts the client. This method must be called before any other method is + * invoked. The client is considered to be started after + * InvalidationListener::Ready has received by the application. + * + * REQUIRES: Start has not already been called. + * The resources given to the client must have been started by the caller. + */ + virtual void Start() = 0; + + /* Stops the client. After this method has been called, it is an error to call + * any other method. + * + * REQUIRES: Start has already been called. + * Does not stop the resources bound to this client. + */ + virtual void Stop() = 0; + + /* Requests that the Ticl register to receive notifications for the object + * with id object_id. The library guarantees that the caller will be informed + * of the results of this call either via + * InvalidationListener::InformRegistrationStatus or + * InvalidationListener::InformRegistrationFailure unless the library informs + * the caller of a connection failure via + * InvalidationListener::InformError. The caller should consider the + * registration to have succeeded only if it gets a call + * InvalidationListener::InformRegistrationStatus for object_id with + * InvalidationListener::RegistrationState::REGISTERED. Note that if the + * network is disconnected, the listener events will probably show up when the + * network connection is repaired. + * + * REQUIRES: Start has been called and and InvalidationListener::Ready has + * been received by the application's listener. + */ + virtual void Register(const ObjectId& object_id) = 0; + + /* Registrations for multiple objects. See the specs on Register(const + * ObjectId&) for more details. If the caller needs to register for a number + * of object ids, this method is more efficient than calling Register in a + * loop. + */ + virtual void Register(const vector<ObjectId>& object_ids) = 0; + + /* Requests that the Ticl unregister for notifications for the object with id + * object_id. The library guarantees that the caller will be informed of the + * results of this call either via + * InvalidationListener::InformRegistrationStatus or + * InvalidationListener::InformRegistrationFailure unless the library informs + * the caller of a connection failure via + * InvalidationListener::InformError. The caller should consider the + * unregistration to have succeeded only if it gets a call + * InvalidationListener::InformRegistrationStatus for object_id with + * InvalidationListener::RegistrationState::UNREGISTERED. Note that if the + * network is disconnected, the listener events will probably show up when the + * network connection is repaired. + * + * REQUIRES: Start has been called and and InvalidationListener::Ready has + * been receiveed by the application's listener. + */ + virtual void Unregister(const ObjectId& object_id) = 0; + + /* Unregistrations for multiple objects. See the specs on Unregister(const + * ObjectId&) for more details. If the caller needs to unregister for a number + * of object ids, this method is more efficient than calling Unregister in a + * loop. + */ + virtual void Unregister(const vector<ObjectId>& object_ids) = 0; + + /* Acknowledges the InvalidationListener event that was delivered with the + * provided acknowledgement handle. This indicates that the client has + * accepted responsibility for processing the event and it does not need to be + * redelivered later. + * + * REQUIRES: Start been called and and InvalidationListener::Ready has been + * received by the application's listener. + */ + virtual void Acknowledge(const AckHandle& ackHandle) = 0; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_CLIENT_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-listener.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-listener.h new file mode 100644 index 0000000..aa881e4 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-listener.h @@ -0,0 +1,193 @@ +// Copyright 2012 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. + +// Interface through which invalidation-related events are delivered by the +// library to the application. Each event must be acknowledged by the +// application. Each includes an AckHandle that the application must use to call +// InvalidationClient::Acknowledge after it is done handling that event. + +#ifndef GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_LISTENER_H_ +#define GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_LISTENER_H_ + +#include <string> + +#include "google/cacheinvalidation/deps/stl-namespace.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::string; + +class AckHandle; +class ErrorInfo; +class Invalidation; +class InvalidationClient; +class ObjectId; + +class InvalidationListener { + public: + /* Possible registration states for an object. */ + enum RegistrationState { + REGISTERED, + UNREGISTERED + }; + + virtual ~InvalidationListener() {} + + /* Called in response to the InvalidationClient::Start call. Indicates that + * the InvalidationClient is now ready for use, i.e., calls such as + * register/unregister can be performed on that object. + * + * Arguments: + * client - the InvalidationClient invoking the listener + */ + virtual void Ready(InvalidationClient* client) = 0; + + /* Indicates that an object has been updated to a particular version. + * + * The Ticl guarantees that this callback will be invoked at least once for + * every invalidation that it guaranteed to deliver. It does not guarantee + * exactly-once delivery or in-order delivery (with respect to the version + * number). + * + * The application should acknowledge this event by calling + * InvalidationClient::Acknowledge(const AckHandle&) with the provided + * ack_handle otherwise the event may be redelivered. + * + * Arguments: + * client - the InvalidationClient invoking the listener + * ack_handle - event acknowledgement handle + */ + virtual void Invalidate(InvalidationClient* client, + const Invalidation& invalidation, + const AckHandle& ack_handle) = 0; + + /* As Invalidate, but for an unknown application store version. The object may + * or may not have been updated - to ensure that the application does not miss + * an update from its backend, the application must check and/or fetch the + * latest version from its store. + */ + virtual void InvalidateUnknownVersion(InvalidationClient* client, + const ObjectId& object_id, + const AckHandle& ack_handle) = 0; + + /* Indicates that the application should consider all objects to have changed. + * This event is generally sent when the client has been disconnected from the + * network for too long a period and has been unable to resynchronize with the + * update stream, but it may be invoked arbitrarily (although the service + * tries hard not to invoke it under normal circumstances). + * + * The application should acknowledge this event by calling + * InvalidationClient::Acknowledge(const AckHandle&) with the provided + * ack_handle otherwise the event may be redelivered. + * + * Arguments: + * client - the InvalidationClient invoking the listener + * ack_handle - event acknowledgement handle + */ + virtual void InvalidateAll(InvalidationClient* client, + const AckHandle& ack_handle) = 0; + + /* Indicates that the registration state of an object has changed. + * + * The application should acknowledge this event by calling + * InvalidationClient::Acknowledge(AckHandle) with the provided ack_handle; + * otherwise the event may be redelivered. + * + * Arguments: + * client - the InvalidationClient invoking the listener + * object_id - the id of the object whose state changed + * reg_state - the new state + */ + virtual void InformRegistrationStatus(InvalidationClient* client, + const ObjectId& object_id, + RegistrationState reg_state) = 0; + + /* Indicates that an object registration or unregistration operation may have + * failed. + * + * The application should acknowledge this event by calling + * InvalidationClient::acknowledge(AckHandle) with the provided ack_handle; + * otherwise the event may be redelivered. + * + * For transient failures, the application can retry the registration later - + * if it chooses to do so, it must use a sensible backoff policy such as + * exponential backoff. For permanent failures, it must not automatically + * retry without fixing the situation (e.g., by presenting a dialog box to the + * user). + * + * Arguments: + * client - the {@link InvalidationClient} invoking the listener + * object_id - the id of the object whose state changed + * is_transient - whether the error is transient or permanent + * errorMessage - extra information about the message + */ + virtual void InformRegistrationFailure(InvalidationClient* client, + const ObjectId& object_id, + bool is_transient, + const string& error_message) = 0; + + /* Indicates that the all registrations for the client are in an unknown state + * (e.g., they could have been removed). The application MUST inform the + * InvalidationClient of its registrations once it receives this event. The + * requested objects are those for which the digest of their serialized object + * ids matches a particular prefix bit-pattern. The digest for an object id is + * computed as following (the digest chosen for this method is SHA-1): + * + * Digest digest(); + * digest.Update(Little endian encoding of object source type) + * digest.Update(object name) + * digest.GetDigestSummary() + * + * For a set of objects, digest is computed by sorting lexicographically based + * on their digests and then performing the update process given above (i.e., + * calling digest.update on each object's digest and then calling + * getDigestSummary at the end). + * + * IMPORTANT: A client can always register for more objects than what is + * requested here. For example, in response to this call, the client can + * ignore the prefix parameters and register for all its objects. + * + * The application should acknowledge this event by calling + * InvalidationClient::Acknowledge(const AckHandle&) with the provided + * ack_handle otherwise the event may be redelivered. The acknowledge using + * ack_handle must be called after all the InvalidationClient::Register calls + * have been made. + * + * Arguments: + * client - the InvalidationClient invoking the listener + * prefix - prefix of the object ids as described above. + * prefix_length - number of bits in prefix to consider. + */ + virtual void ReissueRegistrations(InvalidationClient* client, + const string& prefix, + int prefix_length) = 0; + + /* Informs the listener about errors that have occurred in the backend, e.g., + * authentication, authorization problems. + * + * The application should acknowledge this event by calling + * InvalidationClient::Acknowledge(const AckHandle&) with the provided + * ack_handle otherwise the event may be redelivered. + * + * Arguments: + * client - the InvalidationClient invoking the listener + * error_info - information about the error + */ + virtual void InformError(InvalidationClient* client, + const ErrorInfo& error_info) = 0; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_INCLUDE_INVALIDATION_LISTENER_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/include/system-resources.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/system-resources.h new file mode 100644 index 0000000..979140c --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/system-resources.h @@ -0,0 +1,266 @@ +// Copyright 2012 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. + +// Interfaces for the system resources used by the Ticl. System resources are an +// abstraction layer over the host operating system that provides the Ticl with +// the ability to schedule events, send network messages, store data, and +// perform logging. +// +// NOTE: All the resource types and SystemResources are required to be +// thread-safe. + +#ifndef GOOGLE_CACHEINVALIDATION_INCLUDE_SYSTEM_RESOURCES_H_ +#define GOOGLE_CACHEINVALIDATION_INCLUDE_SYSTEM_RESOURCES_H_ + +#include <string> +#include <utility> + +#include "google/cacheinvalidation/deps/callback.h" +#include "google/cacheinvalidation/deps/stl-namespace.h" +#include "google/cacheinvalidation/deps/time.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::pair; +using INVALIDATION_STL_NAMESPACE::string; + +class Status; +class SystemResources; // Declared below. + +typedef pair<Status, string> StatusStringPair; +typedef INVALIDATION_CALLBACK1_TYPE(string) MessageCallback; +typedef INVALIDATION_CALLBACK1_TYPE(bool) NetworkStatusCallback; +typedef INVALIDATION_CALLBACK1_TYPE(StatusStringPair) ReadKeyCallback; +typedef INVALIDATION_CALLBACK1_TYPE(Status) WriteKeyCallback; +typedef INVALIDATION_CALLBACK1_TYPE(bool) DeleteKeyCallback; +typedef INVALIDATION_CALLBACK1_TYPE(StatusStringPair) ReadAllKeysCallback; + +/* Interface for a component of a SystemResources implementation constructed by + * calls to set* methods of SystemResourcesBuilder. + * + * The SystemResourcesBuilder allows applications to create a single + * SystemResources implementation by composing individual building blocks, each + * of which implements one of the four required interfaces (Logger, Storage, + * NetworkChannel, Scheduler). + * + * However, each interface implementation may require functionality from + * another. For example, the network implementation may need to do logging. In + * order to allow this, we require that the interface implementations also + * implement ResourceComponent, which specifies the single method + * SetSystemResources. It is guaranteed that this method will be invoked exactly + * once on each interface implementation and before any other calls are + * made. Implementations can then save a reference to the provided resources for + * later use. + * + * Note: for the obvious reasons of infinite recursion, implementations should + * not attempt to access themselves through the provided SystemResources. + */ +class ResourceComponent { + public: + virtual ~ResourceComponent() {} + + /* Supplies a |SystemResources| instance to the component. */ + virtual void SetSystemResources(SystemResources* resources) = 0; +}; + +/* Interface specifying the logging functionality provided by + * SystemResources. + */ +class Logger : public ResourceComponent { + public: + enum LogLevel { + FINE_LEVEL, + INFO_LEVEL, + WARNING_LEVEL, + SEVERE_LEVEL + }; + + virtual ~Logger() {} + + /* Logs a message. + * + * Arguments: + * level - the level at which the message should be logged (e.g., INFO) + * file - the file from which the message is being logged + * line - the line number from which the message is being logged + * template - the string to log, optionally containing %s sequences + * ... - values to substitute for %s sequences in template + */ + virtual void Log(LogLevel level, const char* file, int line, + const char* format, ...) = 0; +}; + +/* Interface specifying the scheduling functionality provided by + * SystemResources. + */ +class Scheduler : public ResourceComponent { + public: + virtual ~Scheduler() {} + + /* Function returning a zero time delta, for readability. */ + static TimeDelta NoDelay() { + return TimeDelta::FromMilliseconds(0); + } + + /* Schedules runnable to be run on scheduler's thread after at least + * delay. + * Callee owns the runnable and must delete it after the task has run + * (or if the scheduler is shut down before the task has run). + */ + virtual void Schedule(TimeDelta delay, Closure* runnable) = 0; + + /* Returns whether the current code is executing on the scheduler's thread. + */ + virtual bool IsRunningOnThread() const = 0; + + /* Returns the current time in milliseconds since *some* epoch (NOT + * necessarily the UNIX epoch). The only requirement is that this time + * advance at the rate of real time. + */ + virtual Time GetCurrentTime() const = 0; +}; + +/* Interface specifying the network functionality provided by + * SystemResources. + */ +class NetworkChannel : public ResourceComponent { + public: + virtual ~NetworkChannel() {} + + /* Sends outgoing_message to the data center. */ + // Implementation note: this is currently a serialized ClientToServerMessage + // protocol buffer. Implementors MAY NOT rely on this fact. + virtual void SendMessage(const string& outgoing_message) = 0; + + /* Sets the receiver to which messages from the data center will be delivered. + * Ownership of |incoming_receiver| is transferred to the network channel. + */ + // Implementation note: this is currently a serialized ServerToClientMessage + // protocol buffer. Implementors MAY NOT rely on this fact. + virtual void SetMessageReceiver(MessageCallback* incoming_receiver) = 0; + + /* Informs the network channel that network_status_receiver be informed about + * changes to network status changes. If the network is connected, the channel + * should call network_Status_Receiver->Run(true) and when the network is + * disconnected, it should call network_status_receiver->Run(false). Note that + * multiple receivers can be registered with the channel to receive such + * status updates. + * + * The informing of the status to the network_status_receiver can be + * implemented in a best-effort manner with the caveat that indicating + * incorrectly that the network is connected can result in unnecessary calls + * for SendMessage. Incorrect information that the network is disconnected can + * result in messages not being sent by the client library. + * + * Ownership of network_status_receiver is transferred to the network channel. + */ + virtual void AddNetworkStatusReceiver( + NetworkStatusCallback* network_status_receiver) = 0; +}; + +/* Interface specifying the storage functionality provided by + * SystemResources. Basically, the required functionality is a small subset of + * the method of a regular hash map. + */ +class Storage : public ResourceComponent { + public: + virtual ~Storage() {} + + /* Attempts to persist value for the given key. Invokes done when finished, + * passing a value that indicates whether it was successful. + * + * Note: If a wrie W1 finishes unsuccessfully and then W2 is issued for the + * same key and W2 finishes successfully, W1 must NOT later overwrite W2. + * Callee owns |done| after this call. After it calls |done->Run()|, it must + * delete |done|. + * + * REQUIRES: Neither key nor value is null. + */ + virtual void WriteKey(const string& key, const string& value, + WriteKeyCallback* done) = 0; + + /* Reads the value corresponding to key and calls done with the result. If it + * finds the key, passes a success status and the value. Else passes a failure + * status and a null value. + * Callee owns |done| after this call. After it calls |done->Run()|, it must + * delete |done|. + */ + virtual void ReadKey(const string& key, ReadKeyCallback* done) = 0; + + /* Deletes the key, value pair corresponding to key. If the deletion succeeds, + * calls done with true; else calls it with false. + * Callee owns |done| after this call. After it calls |done->Run()|, it must + * delete |done|. + */ + virtual void DeleteKey(const string& key, DeleteKeyCallback* done) = 0; + + /* Reads all the keys from the underlying store and then calls key_callback + * with each key that was written earlier and not deleted. When all the keys + * are done, calls key_callback with null. With each key, the code can + * indicate a failed status, in which case the iteration stops. + * Caller continues to own |key_callback|. + */ + virtual void ReadAllKeys(ReadAllKeysCallback* key_callback) = 0; +}; + +class SystemResources { + public: + virtual ~SystemResources() {} + + /* Starts the resources. + * + * REQUIRES: This method is called before the resources are used. + */ + virtual void Start() = 0; + + /* Stops the resources. After this point, all the resources will eventually + * stop doing any work (e.g., scheduling, sending/receiving messages from the + * network etc). They will eventually convert any further operations to + * no-ops. + * + * REQUIRES: Start has been called. + */ + virtual void Stop() = 0; + + /* Returns whether the resources are started. */ + virtual bool IsStarted() const = 0; + + /* Returns information about the client operating system/platform, e.g., + * Windows, ChromeOS (for debugging/monitoring purposes). + */ + virtual string platform() const = 0; + + /* Returns an object that can be used to do logging. */ + virtual Logger* logger() = 0; + + /* Returns an object that can be used to persist data locally. */ + virtual Storage* storage() = 0; + + /* Returns an object that can be used to send and receive messages. */ + virtual NetworkChannel* network() = 0; + + /* Returns an object that can be used by the client library to schedule its + * internal events. + */ + virtual Scheduler* internal_scheduler() = 0; + + /* Returns an object that can be used to schedule events for the + * application. + */ + virtual Scheduler* listener_scheduler() = 0; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_INCLUDE_SYSTEM_RESOURCES_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/include/types.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/types.h new file mode 100644 index 0000000..4e3be27 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/include/types.h @@ -0,0 +1,369 @@ +// Copyright 2012 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. + +// Types used by the invalidation client library and its applications. + +#ifndef GOOGLE_CACHEINVALIDATION_INCLUDE_TYPES_H_ +#define GOOGLE_CACHEINVALIDATION_INCLUDE_TYPES_H_ + +#include <string> + +#include "google/cacheinvalidation/deps/logging.h" +#include "google/cacheinvalidation/deps/stl-namespace.h" + +namespace invalidation { + +using INVALIDATION_STL_NAMESPACE::string; + +/* Represents an opaque handle that can be used to acknowledge an invalidation + * event by calling InvalidationClient::Acknowledge(AckHandle) to indicate that + * the client has successfully handled the event. + */ +class AckHandle { + public: + /* Creates a new ack handle from the serialized handle_data representation. */ + explicit AckHandle(const string& handle_data) : handle_data_(handle_data) {} + + const string& handle_data() const { + return handle_data_; + } + + bool operator==(const AckHandle& ack_handle) const { + return handle_data() == ack_handle.handle_data(); + } + + bool IsNoOp() const { + return handle_data_.empty(); + } + + private: + /* The serialized representation of the handle. */ + string handle_data_; +}; + +/* An identifier for application clients in an application-defined way. I.e., a + * client name in an application naming scheme. This is not interpreted by the + * invalidation system - however, it is used opaquely to squelch invalidations + * for the cient causing an update, e.g., if a client C whose app client id is + * C.appClientId changes object X and the backend store informs the backend + * invalidation sytsem that X was modified by X.appClientId, the invalidation to + * C can then be squelched by the invalidation system. + */ +class ApplicationClientId { + public: + /* Creates an application id for the given client_Name. */ + explicit ApplicationClientId(const string& client_name) + : client_name_(client_name) {} + + const string& client_name() const { + return client_name_; + } + + bool operator==(const ApplicationClientId& app_client_id) const { + return client_name() == app_client_id.client_name(); + } + + private: + string client_name_; +}; + +/* Possible reasons for error in InvalidationListener::InformError. The + * application writer must NOT assume that this is complete list since error + * codes may be added later. That is, for error codes that it cannot handle, + * it should not necessarily just crash the code. It may want to present a + * dialog box to the user (say). For each ErrorReason, the ErrorInfo object + * has a context object. We describe the type and meaning of the context for + * each enum value below. + */ +class ErrorReason { + public: + /* The provided authentication/authorization token is not valid for use. */ + static const int AUTH_FAILURE = 1; + + /* An unknown failure - more human-readable information is in the error + * message. + */ + static const int UNKNOWN_FAILURE = -1; +}; + +/* Extra information about the error - cast to appropriate subtype as specified + * for the reason. + */ +class ErrorContext { + public: + virtual ~ErrorContext() {} +}; + +/* A context with numeric data. */ +class NumberContext : public ErrorContext { + public: + explicit NumberContext(int number) : number_(number) {} + + virtual ~NumberContext() {} + + int number() { + return number_; + } + + private: + int number_; +}; + +/* Information about an error given to the application. */ +class ErrorInfo { + public: + /* Constructs an ErrorInfo object given the reason for the error, whether it + * is transient or permanent, and a helpful message describing the error. + */ + ErrorInfo(int error_reason, bool is_transient, + const string& error_message, const ErrorContext& context) + : error_reason_(error_reason), + is_transient_(is_transient), + error_message_(error_message), + context_(context) {} + + int error_reason() const { + return error_reason_; + } + + bool is_transient() const { + return is_transient_; + } + + const string& error_message() const { + return error_message_; + } + + const ErrorContext& context() const { + return context_; + } + + private: + /* The cause of the failure. */ + int error_reason_; + + /* Is the error transient or permanent. See discussion in Status::Code for + * permanent and transient failure handling. + */ + bool is_transient_; + + /* Human-readable description of the error. */ + string error_message_; + + /* Extra information about the error - cast to appropriate object as specified + * for the reason. + */ + ErrorContext context_; +}; + +/* A class to represent a unique object id that an application can register or + * unregister for. + */ +class ObjectId { + public: + ObjectId() : is_initialized_(false) {} + + /* Creates an object id for the given source and name (the name is copied). */ + ObjectId(int source, const string& name) + : is_initialized_(true), source_(source), name_(name) {} + + void Init(int source, const string& name) { + is_initialized_ = true; + source_ = source; + name_ = name; + } + + int source() const { + CHECK(is_initialized_); + return source_; + } + + const string& name() const { + CHECK(is_initialized_); + return name_; + } + + bool operator==(const ObjectId& object_id) const { + CHECK(is_initialized_); + CHECK(object_id.is_initialized_); + return (source() == object_id.source()) && (name() == object_id.name()); + } + + private: + /* Whether the object id has been initialized. */ + bool is_initialized_; + + /* The invalidation source type. */ + int source_; + + /* The name/unique id for the object. */ + string name_; +}; + +/* A class to represent an invalidation for a given object/version and an + * optional payload. + */ +class Invalidation { + public: + Invalidation() : is_initialized_(false) {} + + /* Creates a restarted invalidation for the given object and version. */ + Invalidation(const ObjectId& object_id, int64 version) { + Init(object_id, version, true); + } + + /* Creates an invalidation for the given object, version, and payload. */ + Invalidation(const ObjectId& object_id, int64 version, + const string& payload) { + Init(object_id, version, payload, true); + } + + /* + * Creates an invalidation for the given object, version, payload, + * and restarted flag. + */ + Invalidation(const ObjectId& object_id, int64 version, const string& payload, + bool is_trickle_restart) { + Init(object_id, version, payload, is_trickle_restart); + } + + + void Init(const ObjectId& object_id, int64 version, bool is_trickle_restart) { + Init(object_id, version, false, "", is_trickle_restart); + } + + void Init(const ObjectId& object_id, int64 version, const string& payload, + bool is_trickle_restart) { + Init(object_id, version, true, payload, is_trickle_restart); + } + + const ObjectId& object_id() const { + return object_id_; + } + + int64 version() const { + return version_; + } + + bool has_payload() const { + return has_payload_; + } + + const string& payload() const { + return payload_; + } + + // This method is for internal use only. + bool is_trickle_restart_for_internal_use() const { + return is_trickle_restart_; + } + + bool operator==(const Invalidation& invalidation) const { + return (object_id() == invalidation.object_id()) && + (version() == invalidation.version()) && + (is_trickle_restart_for_internal_use() == + invalidation.is_trickle_restart_for_internal_use()) && + (has_payload() == invalidation.has_payload()) && + (payload() == invalidation.payload()); + } + + private: + void Init(const ObjectId& object_id, int64 version, bool has_payload, + const string& payload, bool is_trickle_restart) { + is_initialized_ = true; + object_id_.Init(object_id.source(), object_id.name()); + version_ = version; + has_payload_ = has_payload; + payload_ = payload; + is_trickle_restart_ = is_trickle_restart; + } + + /* Whether this invalidation has been initialized. */ + bool is_initialized_; + + /* The object being invalidated/updated. */ + ObjectId object_id_; + + /* The new version of the object. */ + int64 version_; + + /* Whether or not the invalidation includes a payload. */ + bool has_payload_; + + /* Optional payload for the client. */ + string payload_; + + /* Flag whether the trickle restarts at this invalidation. */ + bool is_trickle_restart_; +}; + +/* Information given to about a operation - success, temporary or permanent + * failure. + */ +class Status { + public: + /* Actual status of the operation: Whether successful, transient or permanent + * failure. + */ + enum Code { + /* Operation was successful. */ + SUCCESS, + + /* Operation had a transient failure. The application can retry the failed + * operation later - if it chooses to do so, it must use a sensible backoff + * policy such as exponential backoff. + */ + TRANSIENT_FAILURE, + + /* Opration has a permanent failure. Application must not automatically + * retry without fixing the situation (e.g., by presenting a dialog box to + * the user). + */ + PERMANENT_FAILURE + }; + + /* Creates a new Status object given the code and message. */ + Status(Code code, const string& message) : code_(code), message_(message) {} + + bool IsSuccess() const { + return code_ == SUCCESS; + } + + bool IsTransientFailure() const { + return code_ == TRANSIENT_FAILURE; + } + + bool IsPermanentFailure() const { + return code_ == PERMANENT_FAILURE; + } + + const string& message() const { + return message_; + } + + bool operator==(const Status& status) const { + return (code_ == status.code_) && (message() == status.message()); + } + + private: + /* Success or failure. */ + Code code_; + + /* A message describing why the state was unknown, for debugging. */ + string message_; +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_INCLUDE_TYPES_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/test/deterministic-scheduler.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/deterministic-scheduler.cc new file mode 100644 index 0000000..3def8f7 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/deterministic-scheduler.cc @@ -0,0 +1,84 @@ +// Copyright 2012 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. + +#include "google/cacheinvalidation/test/deterministic-scheduler.h" + +namespace invalidation { + +void DeterministicScheduler::StopScheduler() { + run_state_.Stop(); + // Delete any tasks that haven't been run. + while (!work_queue_.empty()) { + TaskEntry top_elt = work_queue_.top(); + work_queue_.pop(); + delete top_elt.task; + } +} + +void DeterministicScheduler::Schedule(TimeDelta delay, Closure* task) { + CHECK(IsCallbackRepeatable(task)); + CHECK(run_state_.IsStarted()); + TLOG(logger_, INFO, "(Now: %d) Enqueuing %p with delay %d", + current_time_.ToInternalValue(), task, delay.InMilliseconds()); + work_queue_.push(TaskEntry(GetCurrentTime() + delay, current_id_++, task)); +} + +void DeterministicScheduler::PassTime(TimeDelta delta_time, TimeDelta step) { + CHECK(delta_time >= TimeDelta()) << "cannot pass a negative amount of time"; + TimeDelta cumulative = TimeDelta(); + + // Run tasks that are ready to run now. + RunReadyTasks(); + + // Advance in increments of |step| until doing so would cause us to go past + // the requested |delta_time|. + while ((cumulative + step) < delta_time) { + ModifyTime(step); + cumulative += step; + RunReadyTasks(); + } + + // Then advance one final time to finish out the interval. + ModifyTime(delta_time - cumulative); + RunReadyTasks(); +} + +void DeterministicScheduler::RunReadyTasks() { + running_internal_ = true; + while (RunNextTask()) { + continue; + } + running_internal_ = false; +} + +bool DeterministicScheduler::RunNextTask() { + if (!work_queue_.empty()) { + // The queue is not empty, so get the first task and see if its scheduled + // execution time has passed. + TaskEntry top_elt = work_queue_.top(); + if (top_elt.time <= GetCurrentTime()) { + // The task is scheduled to run in the past or present, so remove it + // from the queue and run the task. + work_queue_.pop(); + TLOG(logger_, FINE, "(Now: %d) Running task %p", + current_time_.ToInternalValue(), top_elt.task); + top_elt.task->Run(); + delete top_elt.task; + return true; + } + } + return false; +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/test/deterministic-scheduler.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/deterministic-scheduler.h new file mode 100644 index 0000000..932932f --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/deterministic-scheduler.h @@ -0,0 +1,152 @@ +// Copyright 2012 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. + +// An implementation of the Scheduler interface for unit testing (in a +// single-threaded environment). + +#ifndef GOOGLE_CACHEINVALIDATION_TEST_DETERMINISTIC_SCHEDULER_H_ +#define GOOGLE_CACHEINVALIDATION_TEST_DETERMINISTIC_SCHEDULER_H_ + +#include <queue> +#include <string> +#include <utility> + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/callback.h" +#include "google/cacheinvalidation/deps/logging.h" +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/deps/time.h" +#include "google/cacheinvalidation/impl/log-macro.h" +#include "google/cacheinvalidation/impl/run-state.h" + +namespace invalidation { + +// An entry in the work queue. Ensures that tasks don't run until their +// scheduled time, and for a given time, they run in the order in which they +// were enqueued. +struct TaskEntry { + TaskEntry(Time time, int64 id, Closure* task) + : time(time), id(id), task(task) {} + + bool operator<(const TaskEntry& other) const { + // Priority queue returns *largest* element first. + return (time > other.time) || + ((time == other.time) && (id > other.id)); + } + Time time; // the time at which to run + int64 id; // the order in which this task was enqueued + Closure* task; // the task to be run +}; + +class DeterministicScheduler : public Scheduler { + public: + // Caller retains ownershup of |logger|. + explicit DeterministicScheduler(Logger* logger) + : current_id_(0), running_internal_(false), logger_(logger) {} + + virtual ~DeterministicScheduler() { + StopScheduler(); + } + + virtual void SetSystemResources(SystemResources* resources) { + // Nothing to do. + } + + virtual Time GetCurrentTime() const { + return current_time_; + } + + void StartScheduler() { + run_state_.Start(); + } + + void StopScheduler(); + + virtual void Schedule(TimeDelta delay, Closure* task); + + virtual bool IsRunningOnThread() const { + return running_internal_; + } + + void SetInitialTime(Time new_time) { + current_time_ = new_time; + } + + // Passes |delta_time| in increments of at most |step|, executing all pending + // tasks during that interval. + void PassTime(TimeDelta delta_time, TimeDelta step); + + // Passes |delta_time| in default-sized increments, executing all pending + // tasks. + void PassTime(TimeDelta delta_time) { + PassTime(delta_time, DefaultTimeStep()); + } + + private: + // Runs all the work in the queue that should be executed by the current time. + // Note that tasks run may enqueue additional immediate tasks, and this call + // won't return until they've completed as well. While these tasks are + // running, the running_internal_ flag is set, so IsRunningOnThread() + // will return true. + void RunReadyTasks(); + + // Default time step when simulating passage of time. Chosen to be + // significantly smaller than any scheduling interval used by the client + // library. + static TimeDelta DefaultTimeStep() { + return TimeDelta::FromMilliseconds(10); + } + + void ModifyTime(TimeDelta delta_time) { + current_time_ += delta_time; + } + + // Attempts to run a task, returning true is there was a task to run. + bool RunNextTask(); + + // The current time, which may be set by the test. + Time current_time_; + + // The id number of the next task. + uint64 current_id_; + + // Whether or not the scheduler has been started/stopped. + RunState run_state_; + + // Whether or not we're currently running internal tasks from the internal + // queue. + bool running_internal_; + + // A priority queue on which the actual tasks are enqueued. + std::priority_queue<TaskEntry> work_queue_; + + // A logger. + Logger* logger_; +}; + +// A simple deterministic scheduler that always indicates that it is on +// the correct thread. +class SimpleDeterministicScheduler : public DeterministicScheduler { + public: + explicit SimpleDeterministicScheduler(Logger* logger) + : DeterministicScheduler(logger) {} + + virtual bool IsRunningOnThread() const { + return true; + } +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_TEST_DETERMINISTIC_SCHEDULER_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-logger.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-logger.cc new file mode 100644 index 0000000..74e0a47 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-logger.cc @@ -0,0 +1,53 @@ +// Copyright 2012 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. + +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/test/test-logger.h" + +namespace invalidation { + +TestLogger::~TestLogger() {} + +void TestLogger::Log(LogLevel level, const char* file, int line, + const char* format, ...) { + va_list ap; + va_start(ap, format); + string result; + StringAppendV(&result, format, ap); + switch (level) { + case FINE_LEVEL: + case INFO_LEVEL: + LogMessage(file, line, logging::LOG_INFO).stream() << result; + break; + + case WARNING_LEVEL: + LogMessage(file, line, logging::LOG_WARNING).stream() << result; + break; + + case SEVERE_LEVEL: + LogMessage(file, line, logging::LOG_ERROR).stream() << result; + break; + + default: + LOG(FATAL) << "unknown log level: " << level; + break; + } + va_end(ap); +} + +void TestLogger::SetSystemResources(SystemResources* resources) { + // Nothing to do (logger uses no other resources). +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-logger.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-logger.h new file mode 100644 index 0000000..55de320 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-logger.h @@ -0,0 +1,37 @@ +// Copyright 2012 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. + +#ifndef GOOGLE_CACHEINVALIDATION_TEST_TEST_LOGGER_H_ +#define GOOGLE_CACHEINVALIDATION_TEST_TEST_LOGGER_H_ + +#include "google/cacheinvalidation/include/system-resources.h" +#include "google/cacheinvalidation/deps/logging.h" + +namespace invalidation { + +// A simple logger implementation for testing. +class TestLogger : public Logger { + public: + virtual ~TestLogger(); + + // Overrides from Logger. + virtual void Log(LogLevel level, const char* file, int line, + const char* format, ...); + + virtual void SetSystemResources(SystemResources* resources); +}; + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_TEST_TEST_LOGGER_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-utils.cc b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-utils.cc new file mode 100644 index 0000000..27c602c --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-utils.cc @@ -0,0 +1,254 @@ +// Copyright 2012 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. + +// +// Helper classes for tests including a mock Scheduler, a mock network and a +// mock storage layer. + +#include <set> + +#include "google/cacheinvalidation/impl/proto-converter.h" +#include "google/cacheinvalidation/test/test-utils.h" +#include "google/cacheinvalidation/test/test-logger.h" +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" + +namespace invalidation { + +using ::google::protobuf::io::StringOutputStream; +using ::testing::DeleteArg; +using ::testing::StrictMock; + +// Creates an Action InvokeNetworkStatusCallback<k>() that calls the Run method +// on the kth argument with value |true|. +ACTION_TEMPLATE( + InvokeNetworkStatusCallback, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_0_VALUE_PARAMS()) { + std::tr1::get<k>(args)->Run(true); +} + +const char* UnitTestBase::kClientToken = "Dummy"; + +void UnitTestBase::SetUp() { + // Start time at an arbitrary point, just to make sure we don't depend on it + // being 0. + start_time = Time() + TimeDelta::FromDays(9); + statistics.reset(new Statistics()); + reg_summary.reset(new RegistrationSummary()); + InitZeroRegistrationSummary(reg_summary.get()); + InitSystemResources(); // Set up system resources + message_callback = NULL; + + // Start the scheduler and resources. + internal_scheduler->StartScheduler(); + resources->Start(); +} + +void UnitTestBase::TearDown() { + if (message_callback != NULL) { + delete message_callback; + message_callback = NULL; + } +} + +void UnitTestBase::InitSystemResources() { + logger = new TestLogger(); + + // Use a deterministic scheduler for the protocol handler's internals, since + // we want precise control over when batching intervals expire. + internal_scheduler = new DeterministicScheduler(logger); + internal_scheduler->SetInitialTime(start_time); + + // Use a mock network to let us trap the protocol handler's message receiver + // and its attempts to send messages. + network = new StrictMock<MockNetwork>(); + listener_scheduler = new StrictMock<MockScheduler>(); + + // Storage shouldn't be used by the protocol handler, so use a strict mock + // to catch any accidental calls. + storage = new StrictMock<MockStorage>(); + + // The BasicSystemResources will set itself in the components. + EXPECT_CALL(*network, SetSystemResources(_)); + EXPECT_CALL(*storage, SetSystemResources(_)); + EXPECT_CALL(*listener_scheduler, SetSystemResources(_)); + + // Create the actual resources. + resources.reset(new BasicSystemResources( + logger, internal_scheduler, listener_scheduler, network, storage, + "unit-test")); +} + +void UnitTestBase::InitCommonExpectations() { + // When we construct the protocol handler, it will set a message receiver on + // the network. Intercept the call and save the callback. + EXPECT_CALL(*network, SetMessageReceiver(_)) + .WillOnce(SaveArg<0>(&message_callback)); + + // It will also add a network status receiver. The network channel takes + // ownership. Invoke it once with |true| just to exercise that code path, + // then delete it since we won't need it anymore. + EXPECT_CALL(*network, AddNetworkStatusReceiver(_)) + .WillOnce(DoAll(InvokeNetworkStatusCallback<0>(), DeleteArg<0>())); +} + +void UnitTestBase::InitRegistrationMessage(const vector<ObjectIdP>& oids, + bool is_reg, RegistrationMessage *reg_message) { + RegistrationP::OpType op_type = is_reg ? + RegistrationP::REGISTER : RegistrationP::UNREGISTER; + for (size_t i = 0; i < oids.size(); i++) { + ProtoHelpers::InitRegistrationP( + oids[i], op_type, reg_message->add_registration()); + } +} + +void UnitTestBase::InitErrorMessage(ErrorMessage::Code error_code, + const string& description, ErrorMessage* error_message) { + error_message->set_code(error_code); + error_message->set_description(description); +} + +void UnitTestBase::InitInvalidationMessage(const vector<InvalidationP>& invs, + InvalidationMessage *inv_message) { + for (size_t i = 0; i < invs.size(); ++i) { + inv_message->add_invalidation()->CopyFrom(invs[i]); + } +} + +TimeDelta UnitTestBase::EndOfTestWaitTime() { + return TimeDelta::FromSeconds(50); +} + +TimeDelta UnitTestBase::MessageHandlingDelay() { + return TimeDelta::FromMilliseconds(50); +} + +void UnitTestBase::InitTestObjectIds(int count, vector<ObjectIdP>* object_ids) { + for (int i = 0; i < count; ++i) { + ObjectIdP object_id; + object_id.set_source(ObjectSource_Type_TEST); + object_id.set_name(StringPrintf("oid%d", i)); + object_ids->push_back(object_id); + } +} + +void UnitTestBase::ConvertFromObjectIdProtos( + const vector<ObjectIdP>& oid_protos, vector<ObjectId> *oids) { + for (size_t i = 0; i < oid_protos.size(); ++i) { + ObjectId object_id; + ProtoConverter::ConvertFromObjectIdProto(oid_protos[i], &object_id); + oids->push_back(object_id); + } +} + +void UnitTestBase::ConvertFromInvalidationProtos( + const vector<InvalidationP>& inv_protos, vector<Invalidation> *invs) { + for (size_t i = 0; i < inv_protos.size(); ++i) { + Invalidation inv; + ProtoConverter::ConvertFromInvalidationProto(inv_protos[i], &inv); + invs->push_back(inv); + } +} + +void UnitTestBase::MakeInvalidationsFromObjectIds( + const vector<ObjectIdP>& object_ids, + vector<InvalidationP>* invalidations) { + for (size_t i = 0; i < object_ids.size(); ++i) { + InvalidationP invalidation; + invalidation.mutable_object_id()->CopyFrom(object_ids[i]); + invalidation.set_is_known_version(true); + invalidation.set_is_trickle_restart(true); + + // Pick an arbitrary version number; it shouldn't really matter, but we + // try not to make them correlated too much with the object name. + invalidation.set_version(100 + ((i * 19) % 31)); + invalidations->push_back(invalidation); + } +} + +void UnitTestBase::MakeRegistrationStatusesFromObjectIds( + const vector<ObjectIdP>& object_ids, bool is_reg, bool is_success, + vector<RegistrationStatus>* registration_statuses) { + for (size_t i = 0; i < object_ids.size(); ++i) { + RegistrationStatus registration_status; + registration_status.mutable_registration()->mutable_object_id()->CopyFrom( + object_ids[i]); + registration_status.mutable_registration()->set_op_type( + is_reg ? RegistrationP::REGISTER : RegistrationP::UNREGISTER); + registration_status.mutable_status()->set_code( + is_success ? StatusP::SUCCESS : StatusP::TRANSIENT_FAILURE); + registration_statuses->push_back(registration_status); + } +} + +TimeDelta UnitTestBase::GetMaxDelay(int delay_ms) { + int64 extra_delay_ms = (delay_ms * kDefaultSmearPercent) / 100.0; + return TimeDelta::FromMilliseconds(extra_delay_ms + delay_ms); +} + +TimeDelta UnitTestBase::GetMaxBatchingDelay( + const ProtocolHandlerConfigP& config) { + return GetMaxDelay(config.batching_delay_ms()); +} + +void UnitTestBase::InitZeroRegistrationSummary(RegistrationSummary* summary) { + summary->set_num_registrations(0); + // "\3329..." can trigger MSVC to warn about "octal escape sequence terminated + // by decimal number", so break the string between the two to avoid the + // warning. + string zero_digest( + "\332" "9\243\356^kK\r2U\277\357\225`\030\220\257\330\007\t"); + summary->set_registration_digest(zero_digest); +} + +void UnitTestBase::InitServerHeader(const string& token, ServerHeader* header) { + ProtoHelpers::InitProtocolVersion(header->mutable_protocol_version()); + header->set_client_token(token); + if (reg_summary.get() != NULL) { + header->mutable_registration_summary()->CopyFrom(*reg_summary.get()); + } + + // Use arbitrary server time and message id, since they don't matter. + header->set_server_time_ms(314159265); + header->set_message_id("message-id-for-test"); +} + +bool UnitTestBase::CompareMessages( + const ::google::protobuf::MessageLite& expected, + const ::google::protobuf::MessageLite& actual) { + // TODO: Fill in proper implementation. + return true; +} + +void UnitTestBase::ProcessIncomingMessage(const ServerToClientMessage& message, + TimeDelta delay) { + string serialized; + message.SerializeToString(&serialized); + message_callback->Run(serialized); + internal_scheduler->PassTime(delay); +} + +Matcher<ClientHeader> UnitTestBase::ClientHeaderMatches( + const ClientHeader* header) { + return AllOf(Property(&ClientHeader::protocol_version, + EqualsProto(header->protocol_version())), + Property(&ClientHeader::registration_summary, + EqualsProto(header->registration_summary())), + Property(&ClientHeader::max_known_server_time_ms, + header->max_known_server_time_ms()), + Property(&ClientHeader::message_id, + header->message_id())); +} + +} // namespace invalidation diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-utils.h b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-utils.h new file mode 100644 index 0000000..4c55803 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-utils.h @@ -0,0 +1,320 @@ +// Copyright 2012 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. + +// +// Helper classes for tests including a mock Scheduler, a mock network, a +// mock storage layer, and a mock listener. + +#ifndef GOOGLE_CACHEINVALIDATION_TEST_TEST_UTILS_H_ +#define GOOGLE_CACHEINVALIDATION_TEST_TEST_UTILS_H_ + +#include "google/cacheinvalidation/client_protocol.pb.h" +#include "google/cacheinvalidation/include/invalidation-listener.h" +#include "google/cacheinvalidation/include/types.h" +#include "google/cacheinvalidation/types.pb.h" +#include "google/cacheinvalidation/deps/gmock.h" +#include "google/cacheinvalidation/deps/string_util.h" +#include "google/cacheinvalidation/impl/basic-system-resources.h" +#include "google/cacheinvalidation/impl/constants.h" +#include "google/cacheinvalidation/impl/log-macro.h" +#include "google/cacheinvalidation/impl/protocol-handler.h" +#include "google/cacheinvalidation/impl/statistics.h" +#include "google/cacheinvalidation/test/deterministic-scheduler.h" + +namespace invalidation { + +using ::ipc::invalidation::ObjectSource_Type_TEST; +using ::ipc::invalidation::ClientHeader; +using ::ipc::invalidation::ClientVersion; +using ::ipc::invalidation::InvalidationP; +using ::ipc::invalidation::ObjectIdP; +using ::ipc::invalidation::ProtocolVersion; +using ::ipc::invalidation::RegistrationP_OpType_REGISTER; +using ::ipc::invalidation::RegistrationP_OpType_UNREGISTER; +using ::ipc::invalidation::RegistrationStatus; +using ::ipc::invalidation::RegistrationSummary; +using ::ipc::invalidation::ServerHeader; +using ::ipc::invalidation::ServerToClientMessage; +using ::ipc::invalidation::StatusP_Code_SUCCESS; +using ::ipc::invalidation::Version; +using ::google::protobuf::MessageLite; +using ::testing::_; +using ::testing::EqualsProto; +using ::testing::Matcher; +using ::testing::Property; +using ::testing::SaveArg; +using ::testing::StrictMock; + +/* A random class whose RandDouble method always returns a given constant. */ +class FakeRandom : public Random { + public: + // Constructs a fake random generator that always returns |return_value|, + // which must be in the range [0, 1). + explicit FakeRandom(double return_value) + : Random(0), return_value_(return_value) { + CHECK_GE(return_value_, 0.0); + CHECK_LT(return_value_, 1.0); + } + + virtual ~FakeRandom() {} + + virtual double RandDouble() { + return return_value_; + } + + private: + double return_value_; +}; + +// A mock of the Scheduler interface. +class MockScheduler : public Scheduler { + public: + MOCK_METHOD2(Schedule, void(TimeDelta, Closure*)); // NOLINT + MOCK_CONST_METHOD0(IsRunningOnThread, bool()); + MOCK_CONST_METHOD0(GetCurrentTime, Time()); + MOCK_METHOD1(SetSystemResources, void(SystemResources*)); // NOLINT +}; + +// A mock of the Network interface. +class MockNetwork : public NetworkChannel { + public: + MOCK_METHOD1(SendMessage, void(const string&)); // NOLINT + MOCK_METHOD1(SetMessageReceiver, void(MessageCallback*)); // NOLINT + MOCK_METHOD1(AddNetworkStatusReceiver, void(NetworkStatusCallback*)); // NOLINT + MOCK_METHOD1(SetSystemResources, void(SystemResources*)); // NOLINT +}; + +// A mock of the Storage interface. +class MockStorage : public Storage { + public: + MOCK_METHOD3(WriteKey, void(const string&, const string&, WriteKeyCallback*)); // NOLINT + MOCK_METHOD2(ReadKey, void(const string&, ReadKeyCallback*)); // NOLINT + MOCK_METHOD2(DeleteKey, void(const string&, DeleteKeyCallback*)); // NOLINT + MOCK_METHOD1(ReadAllKeys, void(ReadAllKeysCallback*)); // NOLINT + MOCK_METHOD1(SetSystemResources, void(SystemResources*)); // NOLINT +}; + +// A mock of the InvalidationListener interface. +class MockInvalidationListener : public InvalidationListener { + public: + MOCK_METHOD1(Ready, void(InvalidationClient*)); // NOLINT + + MOCK_METHOD3(Invalidate, + void(InvalidationClient *, const Invalidation&, // NOLINT + const AckHandle&)); // NOLINT + + MOCK_METHOD3(InvalidateUnknownVersion, + void(InvalidationClient *, const ObjectId&, + const AckHandle&)); // NOLINT + + MOCK_METHOD2(InvalidateAll, + void(InvalidationClient *, const AckHandle&)); // NOLINT + + MOCK_METHOD3(InformRegistrationStatus, + void(InvalidationClient*, const ObjectId&, RegistrationState)); // NOLINT + + MOCK_METHOD4(InformRegistrationFailure, + void(InvalidationClient*, const ObjectId&, bool, const string&)); + + MOCK_METHOD3(ReissueRegistrations, + void(InvalidationClient*, const string&, int)); + + MOCK_METHOD2(InformError, + void(InvalidationClient*, const ErrorInfo&)); +}; + +// A base class for unit tests to share common methods and helper routines. +class UnitTestBase : public testing::Test { + public: + // Default smearing to be done to randomize delays. Zero to get + // precise delays. + static const int kDefaultSmearPercent = 0; + + // The token or nonce used by default for a client in client to server or + // server to client messages. + static const char *kClientToken; + + // When "waiting" at the end of a test to make sure nothing happens, how long + // to wait. + static TimeDelta EndOfTestWaitTime(); + + // When "waiting" at the end of a test to make sure nothing happens, how long + // to wait. This delay will not only allow to run the processing on the + // workqueue but will also give some 'extra' time to the code to do other + // (incorrect) activities if there is a bug, e.g., make an uneccessary + // listener call, etc. + static TimeDelta MessageHandlingDelay(); + + // Populates |object_ids| with |count| object ids in the TEST id space, each + // named oid<n>. + static void InitTestObjectIds(int count, vector<ObjectIdP>* object_ids); + + // Converts object id protos in |oid_protos| to ObjecIds in |oids|. + static void ConvertFromObjectIdProtos(const vector<ObjectIdP>& oid_protos, + vector<ObjectId> *oids); + + // Converts invalidation protos in |inv_protos| to Invalidations in |invs|. + static void ConvertFromInvalidationProtos( + const vector<InvalidationP>& inv_protos, vector<Invalidation> *invs); + + // For each object id in |object_ids|, adds an invalidation to |invalidations| + // for that object at an arbitrary version. + static void MakeInvalidationsFromObjectIds( + const vector<ObjectIdP>& object_ids, + vector<InvalidationP>* invalidations); + + // For each object in |object_ids|, makes a SUCCESSful registration status for + // that object, alternating between REGISTER and UNREGISTER. The precise + // contents of these messages are unimportant to the protocol handler; we just + // need them to pass the message validator. + static void MakeRegistrationStatusesFromObjectIds( + const vector<ObjectIdP>& object_ids, bool is_reg, bool is_success, + vector<RegistrationStatus>* registration_statuses); + + // Returns the maximum smeared delay possible for |delay_ms| given the + // |Smearer|'s default smearing. + static TimeDelta GetMaxDelay(int delay_ms); + + // Returns the maximum batching delay that a message will incur in the + // protocol handler. + static TimeDelta GetMaxBatchingDelay(const ProtocolHandlerConfigP& config); + + // Initializes |summary| with a registration summary for 0 objects. + static void InitZeroRegistrationSummary(RegistrationSummary* summary); + + // Creates a matcher for the parts of the header that the test can predict. + static Matcher<ClientHeader> ClientHeaderMatches(const ClientHeader* header); + + // Initialize |reg_message| to contain registrations for all objects in |oids| + // with |is_reg| indicating whether the operation is register or unregister. + static void InitRegistrationMessage(const vector<ObjectIdP>& oids, + bool is_reg, RegistrationMessage *reg_message); + + // Initializes |inv_message| to contain the invalidations |invs|. + static void InitInvalidationMessage(const vector<InvalidationP>& invs, + InvalidationMessage *inv_message); + + // Initializes |error_message| to contain given the |error_code| and error + // |description|. + static void InitErrorMessage(ErrorMessage::Code error_code, + const string& description, ErrorMessage* error_message); + + // Performs setup for protocol handler unit tests, e.g. creating resource + // components and setting up common expectations for certain mock objects. + virtual void SetUp(); + + // Tears down the test, e.g., drains any schedulers if needed. + virtual void TearDown(); + + // Initializes the basic system resources, using mocks for various components. + void InitSystemResources(); + + // Sets up some common expectations for the system resources. + void InitCommonExpectations(); + + // Initializes a server header with the given token (registration summary is + // picked up the internal state |reg_summary|). + void InitServerHeader(const string& token, ServerHeader* header); + + // Gives a ServerToClientMessage |message| to the protocol handler and + // passes time in the internal scheduler by |delay| waiting for processing to + // be done. + void ProcessIncomingMessage(const ServerToClientMessage& message, + TimeDelta delay); + + // Returns true iff the messages are equal (with lists interpreted as sets). + bool CompareMessages( + const ::google::protobuf::MessageLite& expected, + const ::google::protobuf::MessageLite& actual); + + // Checks that |vec1| and |vec2| contain the same number of elements + // and each element in |vec1| is present in |vec2| and vice-versa (Uses the + // == operator for comparing). Returns true iff it is the case. Note that this + // method will return true for (aab, abb) + template <class T> + static bool CompareVectorsAsSets(const vector<T>& vec1, + const vector<T>& vec2) { + if (vec1.size() != vec2.size()) { + return false; + } + for (size_t i = 0; i < vec1.size(); i++) { + bool found = false; + for (size_t j = 0; (j < vec2.size()) && !found; j++) { + found = found || (vec1[i] == vec2[j]); + } + if (!found) { + return false; + } + } + return true; + } + + // + // Internal state + // + + // The time at which the test started. Initialized to an arbitrary value to + // ensure that we don't depend on it starting at 0. + Time start_time; + + // Components of BasicSystemResources. It takes ownership of all of these, + // and its destructor deletes them, so we need to create fresh ones for each + // test. + + // Use a deterministic scheduler for the protocol handler's internals, since + // we want precise control over when batching intervals expire. + DeterministicScheduler* internal_scheduler; + + // DeterministicScheduler or MockScheduler depending on the test. + MockScheduler* listener_scheduler; + + // Use a mock network to let us trap the protocol handler's message receiver + // and its attempts to send messages. + MockNetwork* network; + + // A logger. + Logger* logger; + + // Storage shouldn't be used by the protocol handler, so use a strict mock to + // catch any accidental calls. + MockStorage* storage; + + // System resources (owned by the test). + scoped_ptr<BasicSystemResources> resources; + + // Statistics object for counting occurrences of different types of events. + scoped_ptr<Statistics> statistics; + + // Message callback installed by the protocol handler. Captured by the mock + // network. + MessageCallback* message_callback; + + // Registration summary to be placed in messages from the client to the server + // and vice-versa. + scoped_ptr<RegistrationSummary> reg_summary; +}; + +// Creates an action InvokeAndDeleteClosure<k> that invokes the kth closure and +// deletes it after the Run method has been called. +ACTION_TEMPLATE( + InvokeAndDeleteClosure, + HAS_1_TEMPLATE_PARAMS(int, k), + AND_0_VALUE_PARAMS()) { + std::tr1::get<k>(args)->Run(); + delete std::tr1::get<k>(args); +} + +} // namespace invalidation + +#endif // GOOGLE_CACHEINVALIDATION_TEST_TEST_UTILS_H_ diff --git a/third_party/cacheinvalidation/src/google/cacheinvalidation/types.proto b/third_party/cacheinvalidation/src/google/cacheinvalidation/types.proto new file mode 100644 index 0000000..f953b79 --- /dev/null +++ b/third_party/cacheinvalidation/src/google/cacheinvalidation/types.proto @@ -0,0 +1,67 @@ +// 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. + +// Enums definitions for main types in the cache invalidation system. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package ipc.invalidation; + +// The type of client / application. +message ClientType { + enum Type { + INTERNAL = 1; + TEST = 2; // Uncontrolled client space for use by anyone for testing. + DEMO = 4; // A demo client type that can be used for testing. + + // Numbers below 1000 are reserved for internal use. + CHROME_SYNC = 1004; + CHROME_SYNC_ANDROID = 1018; + CHROME_SYNC_IOS = 1038; + CHROME_SYNC_GCM_DESKTOP = 1055; + CHROME_SYNC_GCM_IOS = 1056; + } + optional Type type = 1; +} + +// The property that hosts the object. +message ObjectSource { + // + // NOTE: This enum MUST be kept in sync with ObjectIdP.Source in + // internal.proto. + // + enum Type { + INTERNAL = 1; + TEST = 2; // Uncontrolled object space for use by anyone for testing. + DEMO = 4; // A demo object source that can be used for testing. + + // Numbers below 1000 are reserved for internal use. + CHROME_SYNC = 1004; + COSMO_CHANGELOG = 1014; + CHROME_COMPONENTS = 1025; + CHROME_PUSH_MESSAGING = 1030; + } + optional Type type = 1; +} + +// A dummy message to enclose various enum constant declarations. +message Constants { + // Constants related to object versions. + enum ObjectVersion { + // Version number used to indicate that an object's version is unknown. + UNKNOWN = 0; + } +} diff --git a/third_party/cacheinvalidation/src/java/COPYING b/third_party/cacheinvalidation/src/java/COPYING new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/BaseCommonInvalidationConstants.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/BaseCommonInvalidationConstants.java new file mode 100644 index 0000000..4244c2e --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/BaseCommonInvalidationConstants.java @@ -0,0 +1,40 @@ +/* + * 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.common; + +/** Various constants common to clients and servers used in version 2 of the Ticl. */ +public class BaseCommonInvalidationConstants { + /** Major version of the client library. */ + public static final int CLIENT_MAJOR_VERSION = 3; + + /** + * Minor version of the client library, defined to be equal to the datestamp of the build + * (e.g. 20130401). + */ + public static final int CLIENT_MINOR_VERSION = BuildConstants.BUILD_DATESTAMP; + + /** Major version of the protocol between the client and the server. */ + public static final int PROTOCOL_MAJOR_VERSION = 3; + + /** Minor version of the protocol between the client and the server. */ + public static final int PROTOCOL_MINOR_VERSION = 2; + + /** Major version of the client config. */ + public static final int CONFIG_MAJOR_VERSION = 3; + + /** Minor version of the client config. */ + public static final int CONFIG_MINOR_VERSION = 2; +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/BuildConstants.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/BuildConstants.java new file mode 100644 index 0000000..a8a3a65 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/BuildConstants.java @@ -0,0 +1,37 @@ +/* + * 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. + */ +/* + * Copyright 2013 Google Inc. All Rights Reserved. + * + * 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.common; + +/** Build constant definitions. */ +class BuildConstants { + static final int BUILD_DATESTAMP = 20140825; +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/DigestFunction.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/DigestFunction.java new file mode 100644 index 0000000..421c372 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/DigestFunction.java @@ -0,0 +1,34 @@ +/* + * 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.common; + +/** + * Interface specifying a function to compute digests. + * + */ +public interface DigestFunction { + /** Clears the digest state. */ + void reset(); + + /** Adds {@code data} to the digest being computed. */ + void update(byte[] data); + + /** + * Returns the digest of the data added by {@link #update}. After this call has been made, + * reset must be called before {@link #update} and {@link #getDigest} can be called. + */ + byte[] getDigest(); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/ObjectIdDigestUtils.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/ObjectIdDigestUtils.java new file mode 100644 index 0000000..248803e --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/ObjectIdDigestUtils.java @@ -0,0 +1,102 @@ +/* + * 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.common; + +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.Preconditions; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Digest-related utilities for object ids. + * + */ +public class ObjectIdDigestUtils { + /** + * Implementation of {@link DigestFunction} using SHA-1. + */ + public static class Sha1DigestFunction + implements DigestFunction { + /** Digest implementation. */ + private MessageDigest sha1; + + /** + * Whether the {@link #reset()} method needs to be called. This is set to true + * when {@link #getDigest()} is called and aims to prevent subtle bugs caused by + * failing to reset the object before computing a new digest. + */ + private boolean resetNeeded = false; + + public Sha1DigestFunction() { + try { + this.sha1 = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException exception) { + throw new RuntimeException(exception); + } + } + + @Override + public void reset() { + resetNeeded = false; + sha1.reset(); + } + + @Override + public void update(byte[] data) { + Preconditions.checkState(!resetNeeded); + sha1.update(data); + } + + @Override + public byte[] getDigest() { + Preconditions.checkState(!resetNeeded); + resetNeeded = true; + return sha1.digest(); + } + } + + /** + * Returns the digest of {@code objectIdDigests} using {@code digestFn}. + * <p> + * REQUIRES: {@code objectIdDigests} iterate in sorted order. + */ + public static Bytes getDigest(Iterable<Bytes> objectIdDigests, DigestFunction digestFn) { + digestFn.reset(); + for (Bytes objectIdDigest : objectIdDigests) { + digestFn.update(objectIdDigest.getByteArray()); + } + return new Bytes(digestFn.getDigest()); + } + + /** + * Returns the digest for the object id with source {@code objectSource} and name + * {@code objectName} using {@code digestFn}. + */ + public static Bytes getDigest(int objectSource, byte[] objectName, DigestFunction digestFn) { + digestFn.reset(); + ByteBuffer buffer = ByteBuffer.allocate(Integer.SIZE / 8).order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(objectSource); + + // Little endian number for type followed by bytes. + digestFn.update(buffer.array()); + digestFn.update(objectName); + return new Bytes(digestFn.getDigest()); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClient.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClient.java new file mode 100644 index 0000000..80103a6 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClient.java @@ -0,0 +1,108 @@ +/* + * 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.external.client; + +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.ObjectId; + +import java.util.Collection; + +/** + * Interface for the invalidation client library (Ticl). + * + */ +public interface InvalidationClient { + + /** + * Starts the client. This method MUST be called before any other method is invoked. The client is + * considered to be started after {@link InvalidationListener#ready} has received by the + * application. + * <p> + * REQUIRES: {@link #start} has not already been called. + * Also, the resources given to the client must have been started by the caller. + */ + void start(); + + /** + * Stops the client. After this method has been called, it is an error to call any other method. + * Does not stop the resources bound to this client. + * <p> + * REQUIRES: {@link #start} has already been called. + */ + void stop(); + + /** + * Registers to receive invalidations for the object with id {@code objectId}. + * <p> + * The library guarantees that the caller will be informed of the results of this call either via + * {@link InvalidationListener#informRegistrationStatus} or + * {@link InvalidationListener#informRegistrationFailure}. + * <p> + * The caller should consider the registration to have succeeded only if it gets a call + * {@link InvalidationListener#informRegistrationStatus} for {@code objectId} with + * {@code InvalidationListener.RegistrationState.REGISTERED}. Note that if the network is + * disconnected, the listener events will probably show up after the network connection is + * repaired. + * <p> + * REQUIRES: {@link #start} has been called and {@link InvalidationListener#ready} has been + * received by the application's listener. + */ + void register(ObjectId objectId); + + /** + * Registers to receive invalidations for multiple objects. See the specs on + * {@link #register(ObjectId)} for more details. If the caller needs to register for a number of + * object ids, this method is more efficient than calling {@code register} in a loop. + */ + void register(Collection<ObjectId> objectIds); + + /** + * Unregisters for invalidations for the object with id {@code objectId}. + * <p> + * The library guarantees that the caller will be informed of the results of this call either via + * {@link InvalidationListener#informRegistrationStatus} or + * {@link InvalidationListener#informRegistrationFailure} unless the library informs the caller of + * a connection failure via {@link InvalidationListener#informError}. + * <p> + * The caller should consider the unregistration to have succeeded only if it gets a call + * {@link InvalidationListener#informRegistrationStatus} for {@code objectId} with + * {@link InvalidationListener.RegistrationState#UNREGISTERED}. + * Note that if the network is disconnected, the listener events will probably show up when the + * network connection is repaired. + * <p> + * REQUIRES: {@link #start} has been called and and {@link InvalidationListener#ready} has been + * receiveed by the application's listener. + */ + void unregister(ObjectId objectId); + + /** + * Unregisters for multiple objects. See the specs on {@link #unregister(ObjectId)} for more + * details. If the caller needs to unregister for a number of object ids, this method is more + * efficient than calling {@code unregister} in a loop. + */ + void unregister(Collection<ObjectId> objectIds); + + /** + * Acknowledges the {@link InvalidationListener} event that was delivered with the provided + * acknowledgement handle. This indicates that the client has accepted responsibility for + * processing the event and it does not need to be redelivered later. + * <p> + * REQUIRES: {@link #start} has been called and and {@link InvalidationListener#ready} has been + * received by the application's listener. + */ + void acknowledge(AckHandle ackHandle); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClientConfig.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClientConfig.java new file mode 100644 index 0000000..ca976f2 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClientConfig.java @@ -0,0 +1,43 @@ +/* + * 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.external.client; + +/** + * Application-provided configuration for an invalidation client. + * + */ +public class InvalidationClientConfig { + + /** Client type code as assigned by the notification system's backend. */ + public final int clientType; + + /** Id/name of the client in the application's own naming scheme. */ + public final byte[] clientName; + + /** Name of the application using the library (for debugging/monitoring) */ + public final String applicationName; + + /** If false, invalidateUnknownVersion() is called whenever suppression occurs. */ + public final boolean allowSuppression; + + public InvalidationClientConfig(int clientType, byte[] clientName, String applicationName, + boolean allowSuppression) { + this.clientType = clientType; + this.clientName = clientName; + this.applicationName = applicationName; + this.allowSuppression = allowSuppression; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClientFactory.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClientFactory.java new file mode 100644 index 0000000..a40854a --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClientFactory.java @@ -0,0 +1,49 @@ +/* + * 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.external.client; + +import com.google.ipc.invalidation.ticl.InvalidationClientCore; +import com.google.ipc.invalidation.ticl.InvalidationClientImpl; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; + +import java.util.Random; + +/** + * Factory for creating invalidation clients. + * + */ +public class InvalidationClientFactory { + /** + * Constructs an invalidation client library instance. + * + * @param resources {@link SystemResources} to use for logging, scheduling, persistence, and + * network connectivity + * @param clientConfig application provided configuration for the client. + * @param listener callback object for invalidation events + */ + public static InvalidationClient createClient(SystemResources resources, + InvalidationClientConfig clientConfig, InvalidationListener listener) { + ClientConfigP.Builder internalConfigBuilder = InvalidationClientCore.createConfig().toBuilder(); + internalConfigBuilder.allowSuppression = clientConfig.allowSuppression; + ClientConfigP internalConfig = internalConfigBuilder.build(); + Random random = new Random(resources.getInternalScheduler().getCurrentTimeMs()); + return new InvalidationClientImpl(resources, random, clientConfig.clientType, + clientConfig.clientName, internalConfig, clientConfig.applicationName, listener); + } + + private InvalidationClientFactory() {} // Prevents instantiation. +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationListener.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationListener.java new file mode 100644 index 0000000..3d17d1c --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationListener.java @@ -0,0 +1,177 @@ +/* + * 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.external.client; + +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.external.client.types.Invalidation; +import com.google.ipc.invalidation.external.client.types.ObjectId; + +/** + * Listener Interface that must be implemented by clients to receive object invalidations, + * registration status events, and error events. + * <p> + * After the application publishes an invalidation (oid, version) to , guarantees to send + * at least one of the following events to listeners that have registered for oid: + * <ol> + * <li> Invalidate(oid, version) + * <li> Invalidate(oid, laterVersion) where laterVersion >= version + * <li> InvalidateUnknownVersion(oid) + * </ol> + * <p> + * Each invalidation must be acknowledged by the application. Each includes an AckHandle that + * the application must use to call {@link InvalidationClient#acknowledge} after it is done handling + * that event. + * <p> + * Please see http://go/-api for additional information on the API and semantics. + * <p> + * Please see {@link com.google.ipc.invalidation.external.client.contrib.SimpleListener} for a + * base class that implements some events on behalf of the application. + * <p> + */ +public interface InvalidationListener { + /** Possible registration states for an object. */ + public enum RegistrationState { + REGISTERED, + UNREGISTERED + } + + /** + * Called in response to the {@code InvalidationClient.start} call. Indicates that the + * InvalidationClient is now ready for use, i.e., calls such as register/unregister can be + * performed on that object. + * <p> + * The application MUST NOT issue calls such as register/unregister on the InvalidationClient + * before receiving this event. + * + * @param client the {@link InvalidationClient} invoking the listener + */ + void ready(InvalidationClient client); + + /** + * Indicates that an object has been updated to a particular version. + * <ul> + * <li> When it receives this call, the application MUST hit its backend to fetch the + * updated state of the object, unless it already has has a version at least as recent as that + * of the invalidation. + * + * <li> MAY choose to drop older versions of invalidations, as long as it calls + * {@link #invalidate} with a later version of the same object, or calls + * {@link #invalidateUnknownVersion}. + * + * <li> MAY reorder or duplicate invalidations. + * + * <li> MAY drop a published payload without notice. + * + * <li> The application MUST acknowledge this event by calling + * {@link InvalidationClient#acknowledge} with the provided {@code ackHandle}, otherwise the + * event will be redelivered. + * </ul> + * + * @param client the {@link InvalidationClient} invoking the listener + * @param ackHandle event acknowledgement handle + */ + void invalidate(InvalidationClient client, Invalidation invalidation, AckHandle ackHandle); + + /** + * Indicates that an object has been updated, but the version number and payload are unknown. + * + * <ul> + * <li> When it receives this call, the application MUST hit its backend to fetch the updated + * state of the object, regardless of what version it has in its cache. + * + * <li> The application MUST acknowledge this event by calling + * {@link InvalidationClient#acknowledge} with the provided {@code ackHandle}, otherwise the + * event will be redelivered. + * </ul> + * + * @param client the {@link InvalidationClient} invoking the listener + * @param ackHandle event acknowledgement handle + */ + void invalidateUnknownVersion(InvalidationClient client, ObjectId objectId, + AckHandle ackHandle); + + /** + * Indicates that the application should consider all objects to have changed. This event is sent + * extremely rarely. + * + * <ul> + * <li> The application MUST hit its backend to fetch the updated state of all objects, + * regardless of what version it has in its cache. + * + * <li> The application MUST acknowledge this event by calling + * {@link InvalidationClient#acknowledge} with the provided {@code ackHandle}, otherwise the + * event will be redelivered. + * </ul> + * + * @param client the {@link InvalidationClient} invoking the listener + * @param ackHandle event acknowledgement handle + */ + void invalidateAll(InvalidationClient client, AckHandle ackHandle); + + /** + * Indicates that the registration state of an object has changed. + * + * @param client the {@link InvalidationClient} invoking the listener + * @param objectId the id of the object whose state changed + * @param regState the new state + */ + void informRegistrationStatus(InvalidationClient client, ObjectId objectId, + RegistrationState regState); + + /** + * Indicates that an object registration or unregistration operation may have failed. + * <p> + * For transient failures, the application MAY retry the registration later. If it chooses to do + * so, it MUST use exponential backoff or other sensible backoff policy.. + * <p> + * For permanent failures, the application MUST NOT automatically retry without fixing the + * situation (e.g., by presenting a dialog box to the user). + * + * @param client the {@link InvalidationClient} invoking the listener + * @param objectId the id of the object whose state changed + * @param isTransient whether the error is transient or permanent + * @param errorMessage extra information about the message + */ + void informRegistrationFailure(InvalidationClient client, ObjectId objectId, + boolean isTransient, String errorMessage); + + /** + * Indicates that all registrations for the client are in an unknown state (e.g., may have + * dropped registrations.) + * + * The application MUST call {@link InvalidationClient#register} for all objects that it wishes + * to be registered for. + * + * @param client the {@link InvalidationClient} invoking the listener + * @param ignored clients can ignore this argument. + * @param ignored2 clients can ignore this argument. + */ + void reissueRegistrations(InvalidationClient client, byte[] ignored, int ignored2); + + /** + * Informs the listener about errors that have occurred in the backend. + * + * If the error reason is AUTH_FAILURE, the application may notify the user. + * Otherwise, the application should log the error info for debugging purposes but take no + * other action. + * + * @param client the {@link InvalidationClient} invoking the listener + * @param errorInfo information about the error + */ + void informError(InvalidationClient client, ErrorInfo errorInfo); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/SystemResources.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/SystemResources.java new file mode 100644 index 0000000..e93b73a --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/SystemResources.java @@ -0,0 +1,211 @@ +/* + * 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.external.client; + +import com.google.ipc.invalidation.external.client.types.Callback; +import com.google.ipc.invalidation.external.client.types.SimplePair; +import com.google.ipc.invalidation.external.client.types.Status; +import com.google.ipc.invalidation.util.BaseLogger; + +/** + * Interfaces for the system resources used by the Ticl. System resources are an abstraction layer + * over the host operating system that provides the Ticl with the ability to schedule events, send + * network messages, store data, and perform logging. + * <p> + * NOTE: All the resource types and SystemResources are required to be thread-safe. + * + */ +public interface SystemResources { + + /** Interface specifying the logging functionality provided by {@link SystemResources}. */ + public interface Logger extends BaseLogger, ResourceComponent {} + + /** Interface specifying the scheduling functionality provided by {@link SystemResources}. */ + public interface Scheduler extends ResourceComponent { + + /** Symbolic constant representing no scheduling delay, for readability. */ + static final int NO_DELAY = 0; + + /** + * Schedules {@code runnable} to be run on scheduler's thread after at least {@code delayMs} + * milliseconds. + */ + void schedule(int delayMs, Runnable runnable); + + /** Returns whether the current code is executing on the scheduler's thread. */ + boolean isRunningOnThread(); + + /** + * Returns the current time in milliseconds since *some* epoch (NOT necessarily the UNIX epoch). + * The only requirement is that this time advance at the rate of real time. + */ + long getCurrentTimeMs(); + } + + /** Interface specifying the network functionality provided by {@link SystemResources}. */ + public interface NetworkChannel extends ResourceComponent { + /** Interface implemented by listeners for network events. */ + public interface NetworkListener { + /** Upcall made when a network message has been received from the data center. */ + // Implementation note: this is currently a serialized ServerToClientMessage protocol buffer. + // Implementors MAY NOT rely on this fact. + void onMessageReceived(byte[] message); + + /** + * Upcall made when the network online status has changed. It will be invoked with + * a boolean indicating whether the network is connected. + * <p> + * This is a best-effort upcall. Note that indicating incorrectly that the network is + * connected can result in unnecessary calls for {@link #sendMessage}. Incorrect information + * that the network is disconnected can result in messages not being sent by the client + * library. + */ + void onOnlineStatusChange(boolean isOnline); + + /** + * Upcall made when the network address has changed. Note that the network channel + * implementation is responsible for determining what constitutes the network address and what + * it means to have it change. + * <p> + * This is a best-effort call; however, failure to invoke it may prevent the client from + * receiving messages and cause it to behave as though offline until its next heartbeat. + */ + void onAddressChange(); + } + + /** Sends {@code outgoingMessage} to the data center. */ + // Implementation note: this is currently a serialized ClientToServerMessage protocol buffer. + // Implementors MAY NOT rely on this fact. + void sendMessage(byte[] outgoingMessage); + + /** + * Sets the {@link NetworkListener} to which events will be delivered. + * <p> + * REQUIRES: no listener already be registered. + */ + void setListener(NetworkListener listener); + } + + /** + * Interface specifying the storage functionality provided by {@link SystemResources}. Basically, + * the required functionality is a small subset of the method of a regular hash map. + */ + public interface Storage extends ResourceComponent { + + /** + * Attempts to persist {@code value} for the given {@code key}. Invokes {@code done} when + * finished, passing a value that indicates whether it was successful. + * <p> + * Note: If a wrie W1 finishes unsuccessfully and then W2 is issued for the same key and W2 + * finishes successfully, W1 must NOT later overwrite W2. + * <p> + * REQUIRES: Neither {@code key} nor {@code value} is null. + */ + void writeKey(String key, byte[] value, Callback<Status> done); + + /** + * Reads the value corresponding to {@code key} and calls {@code done} with the result. + * If it finds the key, passes a success status and the value. Else passes a failure status + * and a null value. + */ + void readKey(String key, Callback<SimplePair<Status, byte[]>> done); + + /** + * Deletes the key, value pair corresponding to {@code key}. If the deletion succeeds, calls + * {@code done} with true; else calls it with false. A deletion of a key that does not exist + * is considered to have succeeded. + */ + void deleteKey(String key, Callback<Boolean> done); + + /** + * Reads all the keys from the underlying store and then calls {@code keyCallback} with + * each key that was written earlier and not deleted. When all the keys are done, calls + * {@code keyCallback} with {@code null}. With each key, the code can indicate a + * failed status, in which case the iteration stops. + */ + void readAllKeys(Callback<SimplePair<Status, String>> keyCallback); + } + + /** + * Interface for a component of a {@link SystemResources} implementation constructed by calls to + * set* methods of {@link SystemResourcesBuilder}. + * <p> + * The SystemResourcesBuilder allows applications to create a single {@link SystemResources} + * implementation by composing individual building blocks, each of which implements one of the + * four required interfaces ({@link Logger}, {@link Storage}, {@link NetworkChannel}, + * {@link Scheduler}). + * <p> + * However, each interface implementation may require functionality from another. For example, the + * network implementation may need to do logging. In order to allow this, we require that the + * interface implementations also implement {@code ResourceComponent}, which specifies the single + * method {@link #setSystemResources}. It is guaranteed that this method will be invoked exactly + * once on each interface implementation and before any other calls are made. Implementations can + * then save a reference to the provided resources for later use. + * <p> + * Note: for the obvious reasons of infinite recursion, implementations should not attempt to + * access themselves through the provided {@link SystemResources}. + */ + public interface ResourceComponent { + + /** Supplies a {@link SystemResources} instance to the component. */ + void setSystemResources(SystemResources resources); + } + + // + // End of nested interfaces + // + + /** + * Starts the resources. + * <p> + * REQUIRES: This method is called before the resources are used. + */ + void start(); + + /** + * Stops the resources. After this point, all the resources will eventually stop doing any work + * (e.g., scheduling, sending/receiving messages from the network etc). They will eventually + * convert any further operations to no-ops. + * <p> + * REQUIRES: Start has been called. + */ + void stop(); + + /** Returns whether the resources are started. */ + boolean isStarted(); + + /** + * Returns information about the client operating system/platform, e.g., Windows, ChromeOS (for + * debugging/monitoring purposes). + */ + String getPlatform(); + + /** Returns an object that can be used to do logging. */ + Logger getLogger(); + + /** Returns an object that can be used to persist data locally. */ + Storage getStorage(); + + /** Returns an object that can be used to send and receive messages. */ + NetworkChannel getNetwork(); + + /** Returns an object that can be used by the client library to schedule its internal events. */ + Scheduler getInternalScheduler(); + + /** Returns an object that can be used to schedule events for the application. */ + Scheduler getListenerScheduler(); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/SystemResourcesBuilder.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/SystemResourcesBuilder.java new file mode 100644 index 0000000..72aae74 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/SystemResourcesBuilder.java @@ -0,0 +1,174 @@ +/* + * 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.external.client; + +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.SystemResources.NetworkChannel; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.external.client.SystemResources.Storage; +import com.google.ipc.invalidation.ticl.BasicSystemResources; +import com.google.ipc.invalidation.util.Preconditions; + + +/** + * A builder to override some or all resource components in {@code SystemResources} . See + * discussion in {@code ResourceComponent} as well. + * + */ + + // The resources used for constructing the SystemResources in builder. +public class SystemResourcesBuilder { + private Scheduler internalScheduler; + private Scheduler listenerScheduler; + private Logger logger; + private NetworkChannel network; + private Storage storage; + private String platform; + + /** If the build method has been called on this builder. */ + private boolean sealed; + + /** See specs at {@code DefaultResourcesFactory.createDefaultResourcesBuilder}. */ + public SystemResourcesBuilder(Logger logger, Scheduler internalScheduler, + Scheduler listenerScheduler, NetworkChannel network, Storage storage) { + this.logger = logger; + this.internalScheduler = internalScheduler; + this.listenerScheduler = listenerScheduler; + this.network = network; + this.storage = storage; + } + + /** Returns a new builder that shares all the resources of {@code builder} but is not sealed. */ + public SystemResourcesBuilder(SystemResourcesBuilder builder) { + this.logger = builder.logger; + this.internalScheduler = builder.internalScheduler; + this.listenerScheduler = builder.listenerScheduler; + this.network = builder.network; + this.storage = builder.storage; + this.sealed = false; + } + + /** Returns the internal scheduler. */ + public Scheduler getInternalScheduler() { + return internalScheduler; + } + + /** Returns the listener scheduler. */ + public Scheduler getListenerScheduler() { + return listenerScheduler; + } + + /** Returns the network channel. */ + public NetworkChannel getNetwork() { + return network; + } + + /** Returns the logger. */ + public Logger getLogger() { + return logger; + } + + /** Returns the storage. */ + public Storage getStorage() { + return storage; + } + + /** + * Sets the scheduler for scheduling internal events to be {@code internalScheduler}. + * <p> + * REQUIRES: {@link #build} has not been called. + */ + public SystemResourcesBuilder setInternalScheduler(Scheduler internalScheduler) { + Preconditions.checkState(!sealed, "Builder's build method has already been called"); + this.internalScheduler = internalScheduler; + return this; + } + + /** + * Sets the scheduler for scheduling listener events to be {@code listenerScheduler}. + * <p> + * REQUIRES: {@link #build} has not been called. + */ + public SystemResourcesBuilder setListenerScheduler(Scheduler listenerScheduler) { + Preconditions.checkState(!sealed, "Builder's build method has already been called"); + this.listenerScheduler = listenerScheduler; + return this; + } + + /** + * Sets the logger to be {@code logger}. + * <p> + * REQUIRES: {@link #build} has not been called. + */ + public SystemResourcesBuilder setLogger(Logger logger) { + Preconditions.checkState(!sealed, "Builder's build method has already been called"); + this.logger = logger; + return this; + } + + /** + * Sets the network channel for communicating with the server to be {@code network}. + * <p> + * REQUIRES: {@link #build} has not been called. + */ + public SystemResourcesBuilder setNetwork(NetworkChannel network) { + Preconditions.checkState(!sealed, "Builder's build method has already been called"); + this.network = network; + return this; + } + + /** + * Sets the persistence layer to be {@code storage}. + * <p> + * REQUIRES: {@link #build} has not been called. + */ + public SystemResourcesBuilder setStorage(Storage storage) { + Preconditions.checkState(!sealed, "Builder's build method has already been called"); + this.storage = storage; + return this; + } + + /** + * Sets the platform to be {@code platform}. + * <p> + * REQUIRES: {@link #build} has not been called. + */ + public SystemResourcesBuilder setPlatform(String platform) { + Preconditions.checkState(!sealed, "Builder's build method has already been called"); + this.platform = platform; + return this; + } + + /** + * Builds the {@code SystemResources} object with the given resource components and returns it. + * <p> + * Caller must not call any mutation method (on this SystemResourcesBuilder) after + * {@code build} has been called (i.e., build and the set* methods) + */ + public SystemResources build() { + Preconditions.checkState(!sealed, "Builder's build method has already been called"); + seal(); + return new BasicSystemResources(logger, internalScheduler, listenerScheduler, network, storage, + platform); + } + + /** Seals the builder so that no mutation method can be called on this. */ + protected void seal() { + Preconditions.checkState(!sealed, "Builder's already sealed"); + sealed = true; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android/service/AndroidLogger.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android/service/AndroidLogger.java new file mode 100644 index 0000000..301cabd --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android/service/AndroidLogger.java @@ -0,0 +1,230 @@ +/* + * 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.external.client.android.service; + +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.util.Formatter; + +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; + + +/** + * Provides the implementation of {@link Logger} for Android. The logging tag will be based upon the + * top-level class name containing the code invoking the logger (the outer class, not an inner or + * anonymous class name). For severe and warning level messages, the Android logger will also + * dump the stack trace of the first argument if it is a throwable. + */ +public class AndroidLogger implements Logger { + + /** Creates a new AndroidLogger that uses the provided value as the Android logging tag */ + public static AndroidLogger forTag(String tag) { + return new AndroidLogger(tag, null); + } + + /** Creates a new AndroidLogger that will compute a tag value dynamically based upon the class + * that calls into the logger and will prepend the provided prefix (if any) on all + * logged messages. + */ + public static AndroidLogger forPrefix(String prefix) { + return new AndroidLogger(null, prefix); + } + + /** + * If {@code false}, then Log.isLoggable() is called to filter log messages + */ + private static boolean filteringDisabled = false; + + /** + * Maps from a Java {@link Level} to the android {@link Log} priority value used to log + * messages at that level. + */ + private static Map<Level, Integer> levelToPriority = new HashMap<Level, Integer>(); + + static { + // Define the mappings for Java log levels to the associated Android log priorities + levelToPriority.put(Level.INFO, Log.INFO); + levelToPriority.put(Level.WARNING, Log.WARN); + levelToPriority.put(Level.SEVERE, Log.ERROR); + levelToPriority.put(Level.FINE, Log.DEBUG); + levelToPriority.put(Level.FINER, Log.VERBOSE); + levelToPriority.put(Level.FINEST, Log.VERBOSE); + levelToPriority.put(Level.CONFIG, Log.INFO); + } + + /** + * Disables log filtering so all logged messages will be captured. + */ + public static void disableFilteringForTest() { + filteringDisabled = true; + } + + /** + * The default minimum Android log level. We default to 0 to ensure everything is logged. + * This should be a value from the {@link Log} constants. + */ + private static int minimumLogLevel = 0; + + /** + * The maximum length of an Android logging tag. There's no formal constants but the constraint is + * mentioned in the Log javadoc + */ + private static final int MAX_TAG_LENGTH = 23; + + /** Constant tag to use for logged messages (or {@code null} to use topmost class on stack */ + private final String tag; + + /** Prefix added to Android logging messages */ + private final String logPrefix; + + /** Creates a logger that prefixes every logging stmt with {@code logPrefix}. */ + private AndroidLogger(String tag, String logPrefix) { + this.tag = tag; + this.logPrefix = logPrefix; + } + + @Override + public boolean isLoggable(Level level) { + return isLoggable(getTag(), levelToPriority(level)); + } + + @Override + public void log(Level level, String template, Object... args) { + int androidLevel = levelToPriority(level); + String tag = getTag(); + if (isLoggable(tag, androidLevel)) { + Log.println(androidLevel, tag, format(template, args)); + } + } + + @Override + public void severe(String template, Object...args) { + String tag = getTag(); + if (isLoggable(tag, Log.ERROR)) { + // If the first argument is an exception, use the form of Log that will dump a stack trace + if ((args.length > 0) && (args[0] instanceof Throwable)) { + Log.e(tag, format(template, args), (Throwable) args[0]); + } else { + Log.e(tag, format(template, args)); + } + } + } + + @Override + public void warning(String template, Object...args) { + String tag = getTag(); + if (isLoggable(tag, Log.WARN)){ + // If the first argument is an exception, use the form of Log that will dump a stack trace + if ((args.length > 0) && (args[0] instanceof Throwable)) { + Log.w(tag, format(template, args), (Throwable) args[0]); + } else { + Log.w(tag, format(template, args)); + } + } + } + + @Override + public void info(String template, Object...args) { + String tag = getTag(); + if (isLoggable(tag, Log.INFO)) { + Log.i(tag, format(template, args)); + } + } + + @Override + public void fine(String template, Object...args) { + String tag = getTag(); + if (isLoggable(tag, Log.DEBUG)) { + Log.d(tag, format(template, args)); + } + } + + @Override + public void setSystemResources(SystemResources resources) { + // No-op. + } + + /** Given a Java logging level, returns the corresponding Android log priority. */ + private static int levelToPriority(Level level) { + Integer priority = levelToPriority.get(level); + if (priority != null) { + return priority; + } + throw new IllegalArgumentException("Unsupported level: " + level); + } + + /** Formats the content of a logged messages for output, prepending the log prefix if any. */ + private String format(String template, Object...args) { + return (logPrefix != null) ? + ("[" + logPrefix + "] " + Formatter.format(template, args)) : + Formatter.format(template, args); + } + + /** Returns the Android logging tag that should be placed on logged messages */ + private String getTag() { + if (tag != null) { + return tag; + } + + StackTraceElement[] stackTrace = new Throwable().getStackTrace(); + String className = null; + for (int i = 0; i < stackTrace.length; i++) { + className = stackTrace[i].getClassName(); + + // Skip over this class's methods + if (!className.equals(AndroidLogger.class.getName())) { + break; + } + } + + // Compute the unqualified class name w/out any inner class, then truncate to the + // maximum tag length. + int unqualBegin = className.lastIndexOf('.') + 1; + if (unqualBegin < 0) { // should never happen, but be safe + unqualBegin = 0; + } + int unqualEnd = className.indexOf('$', unqualBegin); + if (unqualEnd < 0) { + unqualEnd = className.length(); + } + if ((unqualEnd - unqualBegin) > MAX_TAG_LENGTH) { + unqualEnd = unqualBegin + MAX_TAG_LENGTH; + } + return className.substring(unqualBegin, unqualEnd); + } + + /** + * Add additional constraint on logging. In addition to the normal check of + * {@link Log#isLoggable(String, int)} for logging, this also requires a minimum + * log level of the given value. This should be a value from the {@link Log} constants. + */ + public static void setMinimumAndroidLogLevel(int logLevel) { + minimumLogLevel = logLevel; + } + + /** + * Returns {@code true} is the provided tag/level will produce logged output. + */ + + boolean isLoggable(String tag, int priority) { + return filteringDisabled || (priority >= minimumLogLevel && Log.isLoggable(tag, priority)); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android2/AndroidClientFactory.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android2/AndroidClientFactory.java new file mode 100644 index 0000000..82c53d9 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android2/AndroidClientFactory.java @@ -0,0 +1,53 @@ +/* + * 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.external.client.android2; + +import com.google.ipc.invalidation.ticl.InvalidationClientCore; +import com.google.ipc.invalidation.ticl.android2.AndroidTiclManifest; +import com.google.ipc.invalidation.ticl.android2.ProtocolIntents; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; +import com.google.ipc.invalidation.util.Bytes; + +import android.content.Context; +import android.content.Intent; + +/** + * Factory for creating Android clients. + * + */ +public final class AndroidClientFactory { + /** + * Creates a new client. + * <p> + * REQUIRES: no client exist, or a client exists with the same type and name as provided. In + * the latter case, this call is a no-op. + * + * @param context Android system context + * @param clientType type of the client to create + * @param clientName name of the client to create + */ + public static void createClient(Context context, int clientType, byte[] clientName) { + ClientConfigP config = InvalidationClientCore.createConfig(); + Intent intent = ProtocolIntents.InternalDowncalls.newCreateClientIntent( + clientType, Bytes.fromByteArray(clientName), config, false); + intent.setClassName(context, new AndroidTiclManifest(context).getTiclServiceClass()); + context.startService(intent); + } + + private AndroidClientFactory() { + // Disallow instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android2/AndroidManifest.xml b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android2/AndroidManifest.xml new file mode 100644 index 0000000..840c41f --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android2/AndroidManifest.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- Copyright 2011 Google Inc. All Rights Reserved. --> + <!-- Common configuration settings for application using client invalidation library. --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.ipc.invalidation.client.android2"> + + <!-- App receives GCM messages. --> + <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> + <!-- GCM connects to Google Services. --> + <uses-permission android:name="android.permission.INTERNET" /> + <!-- GCM requires a Google account. --> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="android.permission.USE_CREDENTIALS" /> + <!-- Keeps the processor from sleeping when a message is received. --> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + + <application> + <!-- Ticl service. --> + <service android:exported="false" + android:name="com.google.ipc.invalidation.ticl.android2.TiclService"/> + + <!-- Ticl sender. --> + <service android:exported="false" + android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageSenderService"/> + + <!-- Receiver for scheduler alarms. --> + <receiver android:exported="false" + android:name="com.google.ipc.invalidation.ticl.android2.AndroidInternalScheduler$AlarmReceiver"/> + + <!-- GCM Broadcast Receiver --> + <receiver android:exported="true" + android:name="com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener$GCMReceiver" + android:permission="com.google.android.c2dm.permission.SEND"> + <intent-filter> + <action android:name="com.google.android.c2dm.intent.RECEIVE" /> + <action android:name="com.google.android.c2dm.intent.REGISTRATION" /> + <category android:name="com.google.ipc.invalidation.ticl.android2" /> + </intent-filter> + </receiver> + + <!-- GCM multiplexer --> + <service android:exported="false" + android:name="com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener"> + <meta-data android:name="sender_ids" android:value="ipc.invalidation@gmail.com"/> + </service> + + <!-- Invalidation service multiplexed GCM receiver --> + <service android:exported="false" + android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageReceiverService" + android:enabled="true"/> + <receiver android:exported="false" + android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageReceiverService$Receiver"> + <intent-filter> + <action android:name="com.google.ipc.invalidation.gcmmplex.EVENT" /> + </intent-filter> + </receiver> + </application> +</manifest> diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListener.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListener.java new file mode 100644 index 0000000..b9a4e9a --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListener.java @@ -0,0 +1,661 @@ +/* + * 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.external.client.contrib; + +import com.google.ipc.invalidation.external.client.InvalidationClient; +import com.google.ipc.invalidation.external.client.InvalidationClientConfig; +import com.google.ipc.invalidation.external.client.InvalidationListener; +import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.external.client.types.Invalidation; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.InvalidationClientCore; +import com.google.ipc.invalidation.ticl.ProtoWrapperConverter; +import com.google.ipc.invalidation.ticl.android2.AndroidClock; +import com.google.ipc.invalidation.ticl.android2.AndroidInvalidationListenerIntentMapper; +import com.google.ipc.invalidation.ticl.android2.ProtocolIntents; +import com.google.ipc.invalidation.ticl.android2.channel.AndroidChannelConstants.AuthTokenConstants; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.RegistrationCommand; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.StartCommand; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.Preconditions; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; + +import android.app.IntentService; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + + +/** + * Simplified listener contract for Android clients. Takes care of exponential back-off when + * register or unregister are called for an object after a failure has occurred. Also suppresses + * redundant register requests. + * + * <p>A sample implementation of an {@link AndroidListener} is shown below: + * + * <p><code> + * class ExampleListener extends AndroidListener { + * @Override + * public void reissueRegistrations(byte[] clientId) { + * List<ObjectId> desiredRegistrations = ...; + * register(clientId, desiredRegistrations); + * } + * + * @Override + * public void invalidate(Invalidation invalidation, final byte[] ackHandle) { + * // Track the most recent version of the object (application-specific) and then acknowledge + * // the invalidation. + * ... + * acknowledge(ackHandle); + * } + * + * @Override + * public void informRegistrationFailure(byte[] clientId, ObjectId objectId, + * boolean isTransient, String errorMessage) { + * // Try again if there is a transient failure and we still care whether the object is + * // registered or not. + * if (isTransient) { + * boolean shouldRetry = ...; + * if (shouldRetry) { + * boolean shouldBeRegistered = ...; + * if (shouldBeRegistered) { + * register(clientId, ImmutableList.of(objectId)); + * } else { + * unregister(clientId, ImmutableList.of(objectId)); + * } + * } + * } + * } + * + * ... + * } + * </code> + * + * <p>See {@link com.google.ipc.invalidation.examples.android2} for a complete sample. + * + */ +public abstract class AndroidListener extends IntentService { + + /** External alarm receiver that allows the listener to respond to alarm intents. */ + public static final class AlarmReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(intent); + if (intent.hasExtra(AndroidListenerIntents.EXTRA_REGISTRATION)) { + AndroidListenerIntents.issueAndroidListenerIntent(context, intent); + } + } + } + + /** The logger. */ + private static final Logger logger = AndroidLogger.forPrefix(""); + + /** Initial retry delay for exponential backoff (1 minute). */ + + static int initialMaxDelayMs = (int) TimeUnit.SECONDS.toMillis(60); + + /** Maximum delay factor for exponential backoff (6 hours). */ + + static int maxDelayFactor = 6 * 60; + + /** The last client ID passed to the ready up-call. */ + + static Bytes lastClientIdForTest; + + /** + * Invalidation listener implementation. We implement the interface on a private field rather + * than directly to avoid leaking methods that should not be directly called by the client + * application. The listener must be called only on intent service thread. + */ + private final InvalidationListener invalidationListener = new InvalidationListener() { + @Override + public final void ready(final InvalidationClient client) { + Bytes clientId = state.getClientId(); + AndroidListener.lastClientIdForTest = clientId; + AndroidListener.this.ready(clientId.getByteArray()); + } + + @Override + public final void reissueRegistrations(final InvalidationClient client, byte[] prefix, + int prefixLength) { + AndroidListener.this.reissueRegistrations(state.getClientId().getByteArray()); + } + + @Override + public final void informRegistrationStatus(final InvalidationClient client, + final ObjectId objectId, final RegistrationState regState) { + state.informRegistrationSuccess(objectId); + AndroidListener.this.informRegistrationStatus(state.getClientId().getByteArray(), objectId, + regState); + } + + @Override + public final void informRegistrationFailure(final InvalidationClient client, + final ObjectId objectId, final boolean isTransient, final String errorMessage) { + state.informRegistrationFailure(objectId, isTransient); + AndroidListener.this.informRegistrationFailure(state.getClientId().getByteArray(), objectId, + isTransient, errorMessage); + } + + @Override + public void invalidate(InvalidationClient client, Invalidation invalidation, + AckHandle ackHandle) { + AndroidListener.this.invalidate(invalidation, ackHandle.getHandleData()); + } + + @Override + public void invalidateUnknownVersion(InvalidationClient client, ObjectId objectId, + AckHandle ackHandle) { + AndroidListener.this.invalidateUnknownVersion(objectId, ackHandle.getHandleData()); + } + + @Override + public void invalidateAll(InvalidationClient client, AckHandle ackHandle) { + AndroidListener.this.invalidateAll(ackHandle.getHandleData()); + } + + @Override + public void informError(InvalidationClient client, ErrorInfo errorInfo) { + AndroidListener.this.informError(errorInfo); + } + }; + + /** + * The internal state of the listener. Lazy initialization, triggered by {@link #onHandleIntent}. + */ + private AndroidListenerState state; + + /** The clock to use when scheduling retry call-backs. */ + private final AndroidClock clock = new AndroidClock.SystemClock(); + + /** + * The mapper used to route intents to the invalidation listener. Lazy initialization triggered + * by {@link #onCreate}. + */ + private AndroidInvalidationListenerIntentMapper intentMapper; + + /** Initializes {@link AndroidListener}. */ + protected AndroidListener() { + super(""); + + // If the process dies before an intent is handled, setIntentRedelivery(true) ensures that the + // last intent is redelivered. This optimization is not necessary for correctness: on restart, + // all registrations will be reissued and unacked invalidations will be resent anyways. + setIntentRedelivery(true); + } + + /** See specs for {@link InvalidationClient#start}. */ + public static Intent createStartIntent(Context context, InvalidationClientConfig config) { + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(config); + Preconditions.checkNotNull(config.clientName); + + return AndroidListenerIntents.createStartIntent(context, config.clientType, + Bytes.fromByteArray(config.clientName), config.allowSuppression); + } + + /** See specs for {@link InvalidationClient#start}. */ + public static Intent createStartIntent(Context context, int clientType, byte[] clientName) { + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(clientName); + + final boolean allowSuppression = true; + return AndroidListenerIntents.createStartIntent(context, clientType, + Bytes.fromByteArray(clientName), allowSuppression); + } + + /** See specs for {@link InvalidationClient#stop}. */ + public static Intent createStopIntent(Context context) { + Preconditions.checkNotNull(context); + + return AndroidListenerIntents.createStopIntent(context); + } + + /** + * See specs for {@link InvalidationClient#register}. + * + * @param context the context + * @param clientId identifier for the client service for which we are registering + * @param objectIds the object ids being registered + */ + public static Intent createRegisterIntent(Context context, byte[] clientId, + Iterable<ObjectId> objectIds) { + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(clientId); + Preconditions.checkNotNull(objectIds); + + final boolean isRegister = true; + return AndroidListenerIntents.createRegistrationIntent(context, Bytes.fromByteArray(clientId), + objectIds, isRegister); + } + + /** + * See specs for {@link InvalidationClient#register}. + * + * @param clientId identifier for the client service for which we are registering + * @param objectIds the object ids being registered + */ + public void register(byte[] clientId, Iterable<ObjectId> objectIds) { + Preconditions.checkNotNull(clientId); + Preconditions.checkNotNull(objectIds); + + Context context = getApplicationContext(); + context.startService(createRegisterIntent(context, clientId, objectIds)); + } + + /** + * See specs for {@link InvalidationClient#unregister}. + * + * @param context the context + * @param clientId identifier for the client service for which we are unregistering + * @param objectIds the object ids being unregistered + */ + public static Intent createUnregisterIntent(Context context, byte[] clientId, + Iterable<ObjectId> objectIds) { + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(clientId); + Preconditions.checkNotNull(objectIds); + + final boolean isRegister = false; + return AndroidListenerIntents.createRegistrationIntent(context, Bytes.fromByteArray(clientId), + objectIds, isRegister); + } + + /** + * Sets the authorization token and type used by the invalidation client. Call in response to + * {@link #requestAuthToken} calls. + * + * @param pendingIntent pending intent passed to {@link #requestAuthToken} + * @param authToken authorization token + * @param authType authorization token typo + */ + public static void setAuthToken(Context context, PendingIntent pendingIntent, String authToken, + String authType) { + Preconditions.checkNotNull(pendingIntent); + Preconditions.checkNotNull(authToken); + Preconditions.checkNotNull(authType); + + AndroidListenerIntents.issueAuthTokenResponse(context, pendingIntent, authToken, authType); + } + + /** + * See specs for {@link InvalidationClient#unregister}. + * + * @param clientId identifier for the client service for which we are registering + * @param objectIds the object ids being unregistered + */ + public void unregister(byte[] clientId, Iterable<ObjectId> objectIds) { + Preconditions.checkNotNull(clientId); + Preconditions.checkNotNull(objectIds); + + Context context = getApplicationContext(); + context.startService(createUnregisterIntent(context, clientId, objectIds)); + } + + /** See specs for {@link InvalidationClient#acknowledge}. */ + public static Intent createAcknowledgeIntent(Context context, byte[] ackHandle) { + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(ackHandle); + + return AndroidListenerIntents.createAckIntent(context, ackHandle); + } + + /** See specs for {@link InvalidationClient#acknowledge}. */ + public void acknowledge(byte[] ackHandle) { + Preconditions.checkNotNull(ackHandle); + + Context context = getApplicationContext(); + context.startService(createAcknowledgeIntent(context, ackHandle)); + } + + /** + * See specs for {@link InvalidationListener#ready}. + * + * @param clientId the client identifier that must be passed to {@link #createRegisterIntent} + * and {@link #createUnregisterIntent} + */ + public abstract void ready(byte[] clientId); + + /** + * See specs for {@link InvalidationListener#reissueRegistrations}. + * + * @param clientId the client identifier that must be passed to {@link #createRegisterIntent} + * and {@link #createUnregisterIntent} + */ + public abstract void reissueRegistrations(byte[] clientId); + + /** + * See specs for {@link InvalidationListener#informError}. + */ + public abstract void informError(ErrorInfo errorInfo); + + /** + * See specs for {@link InvalidationListener#invalidate}. + * + * @param invalidation the invalidation + * @param ackHandle event acknowledgment handle + */ + public abstract void invalidate(Invalidation invalidation, byte[] ackHandle); + + /** + * See specs for {@link InvalidationListener#invalidateUnknownVersion}. + * + * @param objectId identifier for the object with unknown version + * @param ackHandle event acknowledgment handle + */ + public abstract void invalidateUnknownVersion(ObjectId objectId, byte[] ackHandle); + + /** + * See specs for {@link InvalidationListener#invalidateAll}. + * + * @param ackHandle event acknowledgment handle + */ + public abstract void invalidateAll(byte[] ackHandle); + + /** + * Read listener state. + * + * @return serialized state or {@code null} if it is not available + */ + public abstract byte[] readState(); + + /** Write listener state to some location. */ + public abstract void writeState(byte[] data); + + /** + * See specs for {@link InvalidationListener#informRegistrationFailure}. + */ + public abstract void informRegistrationFailure(byte[] clientId, ObjectId objectId, + boolean isTransient, String errorMessage); + + /** + * See specs for (@link InvalidationListener#informRegistrationStatus}. + */ + public abstract void informRegistrationStatus(byte[] clientId, ObjectId objectId, + RegistrationState regState); + + /** + * Called when an authorization token is needed. Respond by calling {@link #setAuthToken}. + * + * @param pendingIntent pending intent that must be used in {@link #setAuthToken} response. + * @param invalidAuthToken the existing invalid token or null if none exists. Implementation + * should invalidate the token. + */ + public abstract void requestAuthToken(PendingIntent pendingIntent, + String invalidAuthToken); + + /** + * Handles invalidations received while the client is stopped. An implementation may choose to + * do work in response to these invalidations (delivered best-effort by the invalidation system). + * Not intended for use by most client implementations. + */ + protected void backgroundInvalidateForInternalUse( + @SuppressWarnings("unused") Iterable<Invalidation> invalidations) { + // Ignore background invalidations by default. + } + + @Override + public void onCreate() { + super.onCreate(); + + // Initialize the intent mapper (now that context is available). + intentMapper = new AndroidInvalidationListenerIntentMapper(invalidationListener, this); + } + + /** + * Derived classes may override this method to handle custom intents. This is a recommended + * pattern for invalidation-related intents, e.g. for registration and unregistration. Derived + * classes should call {@code super.onHandleIntent(intent)} for any intents they did not + * handle on their own. + */ + @Override + protected void onHandleIntent(Intent intent) { + if (intent == null) { + return; + } + + // We lazily initialize state in calls to onHandleIntent rather than initializing in onCreate + // because onCreate runs on the UI thread and initializeState performs I/O. + if (state == null) { + initializeState(); + } + + // Handle any intents specific to the AndroidListener. For other intents, defer to the + // intentMapper, which handles listener upcalls corresponding to the InvalidationListener + // methods. + if (!tryHandleAuthTokenRequestIntent(intent) && + !tryHandleRegistrationIntent(intent) && + !tryHandleStartIntent(intent) && + !tryHandleStopIntent(intent) && + !tryHandleAckIntent(intent) && + !tryHandleBackgroundInvalidationsIntent(intent)) { + intentMapper.handleIntent(intent); + } + + // Always check to see if we need to persist state changes after handling an intent. + if (state.getIsDirty()) { + writeState(state.marshal().toByteArray()); + state.resetIsDirty(); + } + } + + /** Returns invalidation client that can be used to trigger intents against the TICL service. */ + private InvalidationClient getClient() { + return intentMapper.client; + } + + /** + * Initializes listener state either from persistent proto (if available) or from scratch. + */ + private void initializeState() { + AndroidListenerProtocol.AndroidListenerState proto = getPersistentState(); + if (proto != null) { + state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor, proto); + } else { + state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor); + } + } + + /** + * Reads and parses persistent state for the listener. Returns {@code null} if the state does not + * exist or is invalid. + */ + private AndroidListenerProtocol.AndroidListenerState getPersistentState() { + // Defer to application code to read the blob containing the state proto. + byte[] stateData = readState(); + try { + if (null != stateData) { + AndroidListenerProtocol.AndroidListenerState state = + AndroidListenerProtocol.AndroidListenerState.parseFrom(stateData); + if (!AndroidListenerProtos.isValidAndroidListenerState(state)) { + logger.warning("Invalid listener state."); + return null; + } + return state; + } + } catch (ValidationException exception) { + logger.warning("Failed to parse listener state: %s", exception); + } + return null; + } + + /** + * Tries to handle a request for an authorization token. Returns {@code true} iff the intent is + * an auth token request. + */ + private boolean tryHandleAuthTokenRequestIntent(Intent intent) { + if (!AndroidListenerIntents.isAuthTokenRequest(intent)) { + return false; + } + + // Check for invalid auth token. Subclass may have to invalidate it if it exists in the call + // to getNewAuthToken. + String invalidAuthToken = intent.getStringExtra( + AuthTokenConstants.EXTRA_INVALIDATE_AUTH_TOKEN); + // Intent also includes a pending intent that we can use to pass back our response. + PendingIntent pendingIntent = intent.getParcelableExtra( + AuthTokenConstants.EXTRA_PENDING_INTENT); + if (pendingIntent == null) { + logger.warning("Authorization request without pending intent extra."); + } else { + // Delegate to client application to figure out what the new token should be and the auth + // type. + requestAuthToken(pendingIntent, invalidAuthToken); + } + return true; + } + + /** Tries to handle a stop intent. Returns {@code true} iff the intent is a stop intent. */ + private boolean tryHandleStopIntent(Intent intent) { + if (!AndroidListenerIntents.isStopIntent(intent)) { + return false; + } + getClient().stop(); + return true; + } + + /** + * Tries to handle a registration intent. Returns {@code true} iff the intent is a registration + * intent. + */ + private boolean tryHandleRegistrationIntent(Intent intent) { + RegistrationCommand command = AndroidListenerIntents.findRegistrationCommand(intent); + if ((command == null) || !AndroidListenerProtos.isValidRegistrationCommand(command)) { + return false; + } + // Make sure the registration is intended for this client. If not, we ignore it (suggests + // there is a new client now). + if (!command.getClientId().equals(state.getClientId())) { + logger.warning("Ignoring registration request for old client. Old ID = %s, New ID = %s", + command.getClientId(), state.getClientId()); + return true; + } + boolean isRegister = command.getIsRegister(); + for (ObjectIdP objectIdP : command.getObjectId()) { + ObjectId objectId = ProtoWrapperConverter.convertFromObjectIdProto(objectIdP); + // We may need to delay the registration command (if it is not already delayed). + int delayMs = 0; + if (!command.getIsDelayed()) { + delayMs = state.getNextDelay(objectId); + } + if (delayMs == 0) { + issueRegistration(objectId, isRegister); + } else { + AndroidListenerIntents.issueDelayedRegistrationIntent(getApplicationContext(), clock, + state.getClientId(), objectId, isRegister, delayMs, state.getNextRequestCode()); + } + } + return true; + } + + /** + * Called when the client application requests a new registration. If a redundant register request + * is made -- i.e. when the application attempts to register an object that is already in the + * {@code AndroidListenerState#desiredRegistrations} collection -- the method returns immediately. + * Unregister requests are never ignored since we can't reliably determine whether an unregister + * request is redundant: our policy on failures of any kind is to remove the registration from + * the {@code AndroidListenerState#desiredRegistrations} collection. + */ + private void issueRegistration(ObjectId objectId, boolean isRegister) { + if (isRegister) { + if (state.addDesiredRegistration(objectId)) { + // Don't bother if we think it's already registered. Note that we remove the object from the + // collection when there is a failure. + getClient().register(objectId); + } + } else { + // Remove the object ID from the desired registration collection so that subsequent attempts + // to re-register are not ignored. + state.removeDesiredRegistration(objectId); + getClient().unregister(objectId); + } + } + + /** Tries to handle a start intent. Returns {@code true} iff the intent is a start intent. */ + private boolean tryHandleStartIntent(Intent intent) { + StartCommand command = AndroidListenerIntents.findStartCommand(intent); + if ((command == null) || !AndroidListenerProtos.isValidStartCommand(command)) { + return false; + } + // Reset the state so that we make no assumptions about desired registrations and can ignore + // messages directed at the wrong instance. + state = new AndroidListenerState(initialMaxDelayMs, maxDelayFactor); + boolean skipStartForTest = false; + ClientConfigP clientConfig = InvalidationClientCore.createConfig(); + if (command.getAllowSuppression() != clientConfig.getAllowSuppression()) { + ClientConfigP.Builder clientConfigBuilder = clientConfig.toBuilder(); + clientConfigBuilder.allowSuppression = command.getAllowSuppression(); + clientConfig = clientConfigBuilder.build(); + } + Intent startIntent = ProtocolIntents.InternalDowncalls.newCreateClientIntent( + command.getClientType(), command.getClientName(), clientConfig, skipStartForTest); + AndroidListenerIntents.issueTiclIntent(getApplicationContext(), startIntent); + return true; + } + + /** Tries to handle an ack intent. Returns {@code true} iff the intent is an ack intent. */ + private boolean tryHandleAckIntent(Intent intent) { + byte[] data = AndroidListenerIntents.findAckHandle(intent); + if (data == null) { + return false; + } + getClient().acknowledge(AckHandle.newInstance(data)); + return true; + } + + /** + * Tries to handle a background invalidation intent. Returns {@code true} iff the intent is a + * background invalidation intent. + */ + private boolean tryHandleBackgroundInvalidationsIntent(Intent intent) { + byte[] data = intent.getByteArrayExtra(ProtocolIntents.BACKGROUND_INVALIDATION_KEY); + if (data == null) { + return false; + } + try { + InvalidationMessage invalidationMessage = InvalidationMessage.parseFrom(data); + List<Invalidation> invalidations = new ArrayList<Invalidation>(); + for (InvalidationP invalidation : invalidationMessage.getInvalidation()) { + invalidations.add(ProtoWrapperConverter.convertFromInvalidationProto(invalidation)); + } + backgroundInvalidateForInternalUse(invalidations); + } catch (ValidationException exception) { + logger.info("Failed to parse background invalidation intent payload: %s", + exception.getMessage()); + } + return false; + } + + /** Returns the current state of the listener, for tests. */ + AndroidListenerState getStateForTest() { + return state; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerIntents.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerIntents.java new file mode 100644 index 0000000..d799ef6 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerIntents.java @@ -0,0 +1,220 @@ +/* + * 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.external.client.contrib; + +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.external.client.contrib.AndroidListener.AlarmReceiver; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.android2.AndroidClock; +import com.google.ipc.invalidation.ticl.android2.AndroidTiclManifest; +import com.google.ipc.invalidation.ticl.android2.channel.AndroidChannelConstants.AuthTokenConstants; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.RegistrationCommand; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.StartCommand; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.content.Context; +import android.content.Intent; + + +/** + * Static helper class supporting construction and decoding of intents issued and handled by the + * {@link AndroidListener}. + * + */ +class AndroidListenerIntents { + + /** The logger. */ + private static final Logger logger = AndroidLogger.forPrefix(""); + + /** Key of Intent byte[] holding a {@link RegistrationCommand} protocol buffer. */ + static final String EXTRA_REGISTRATION = + "com.google.ipc.invalidation.android_listener.REGISTRATION"; + + /** Key of Intent byte[] holding a {@link StartCommand} protocol buffer. */ + static final String EXTRA_START = + "com.google.ipc.invalidation.android_listener.START"; + + /** Key of Intent extra indicating that the client should stop. */ + static final String EXTRA_STOP = + "com.google.ipc.invalidation.android_listener.STOP"; + + /** Key of Intent extra holding a byte[] that is ack handle data. */ + static final String EXTRA_ACK = + "com.google.ipc.invalidation.android_listener.ACK"; + + /** + * Issues the given {@code intent} to the TICL service class registered in the {@code context}. + */ + static void issueTiclIntent(Context context, Intent intent) { + context.startService(intent.setClassName(context, + new AndroidTiclManifest(context).getTiclServiceClass())); + } + + /** + * Issues the given {@code intent} to the {@link AndroidListener} class registered in the + * {@code context}. + */ + static void issueAndroidListenerIntent(Context context, Intent intent) { + context.startService(setAndroidListenerClass(context, intent)); + } + + /** + * Returns the ack handle from the given intent if it has the appropriate extra. Otherwise, + * returns {@code null}. + */ + static byte[] findAckHandle(Intent intent) { + return intent.getByteArrayExtra(EXTRA_ACK); + } + + /** + * Returns {@link RegistrationCommand} extra from the given intent or null if no valid + * registration command exists. + */ + static RegistrationCommand findRegistrationCommand(Intent intent) { + // Check that the extra exists. + byte[] data = intent.getByteArrayExtra(EXTRA_REGISTRATION); + if (null == data) { + return null; + } + + // Attempt to parse the extra. + try { + return RegistrationCommand.parseFrom(data); + } catch (ValidationException exception) { + logger.warning("Received invalid proto: %s", exception); + return null; + } + } + + /** + * Returns {@link StartCommand} extra from the given intent or null if no valid start command + * exists. + */ + static StartCommand findStartCommand(Intent intent) { + // Check that the extra exists. + byte[] data = intent.getByteArrayExtra(EXTRA_START); + if (null == data) { + return null; + } + + // Attempt to parse the extra. + try { + return StartCommand.parseFrom(data); + } catch (ValidationException exception) { + logger.warning("Received invalid proto: %s", exception); + return null; + } + } + + /** Returns {@code true} if the intent has the 'stop' extra. */ + static boolean isStopIntent(Intent intent) { + return intent.hasExtra(EXTRA_STOP); + } + + /** Issues a registration retry with delay. */ + static void issueDelayedRegistrationIntent(Context context, AndroidClock clock, + Bytes clientId, ObjectId objectId, boolean isRegister, int delayMs, int requestCode) { + RegistrationCommand command = isRegister ? + AndroidListenerProtos.newDelayedRegisterCommand(clientId, objectId) : + AndroidListenerProtos.newDelayedUnregisterCommand(clientId, objectId); + Intent intent = new Intent() + .putExtra(EXTRA_REGISTRATION, command.toByteArray()) + .setClass(context, AlarmReceiver.class); + + // Create a pending intent that will cause the AlarmManager to fire the above intent. + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, + PendingIntent.FLAG_ONE_SHOT); + + // Schedule the pending intent after the appropriate delay. + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + long executeMs = clock.nowMs() + delayMs; + alarmManager.set(AlarmManager.RTC, executeMs, pendingIntent); + } + + /** Creates a 'start-client' intent. */ + static Intent createStartIntent(Context context, int clientType, Bytes clientName, + boolean allowSuppression) { + Intent intent = new Intent(); + // Create proto for the start command. + StartCommand command = + AndroidListenerProtos.newStartCommand(clientType, clientName, allowSuppression); + intent.putExtra(EXTRA_START, command.toByteArray()); + return setAndroidListenerClass(context, intent); + } + + /** Creates a 'stop-client' intent. */ + static Intent createStopIntent(Context context) { + // Stop command just has the extra (its content doesn't matter). + Intent intent = new Intent(); + intent.putExtra(EXTRA_STOP, true); + return setAndroidListenerClass(context, intent); + } + + /** Create an ack intent. */ + static Intent createAckIntent(Context context, byte[] ackHandle) { + // Ack intent has an extra containing the ack handle data. + Intent intent = new Intent(); + intent.putExtra(EXTRA_ACK, ackHandle); + return setAndroidListenerClass(context, intent); + } + + /** Constructs an intent with {@link RegistrationCommand} proto. */ + static Intent createRegistrationIntent(Context context, Bytes clientId, + Iterable<ObjectId> objectIds, boolean isRegister) { + // Registration intent has an extra containing the RegistrationCommand proto. + Intent intent = new Intent(); + RegistrationCommand command = + AndroidListenerProtos.newRegistrationCommand(clientId, objectIds, isRegister); + intent.putExtra(EXTRA_REGISTRATION, command.toByteArray()); + return setAndroidListenerClass(context, intent); + } + + /** Sets the appropriate class for {@link AndroidListener} service intents. */ + static Intent setAndroidListenerClass(Context context, Intent intent) { + String simpleListenerClass = new AndroidTiclManifest(context).getListenerServiceClass(); + return intent.setClassName(context, simpleListenerClass); + } + + /** Returns {@code true} iff the given intent is an authorization token request. */ + static boolean isAuthTokenRequest(Intent intent) { + return AuthTokenConstants.ACTION_REQUEST_AUTH_TOKEN.equals(intent.getAction()); + } + + /** + * Given an authorization token request intent and authorization information ({@code authToken} + * and {@code authType}) issues a response. + */ + static void issueAuthTokenResponse(Context context, PendingIntent pendingIntent, String authToken, + String authType) { + Intent responseIntent = new Intent() + .putExtra(AuthTokenConstants.EXTRA_AUTH_TOKEN, authToken) + .putExtra(AuthTokenConstants.EXTRA_AUTH_TOKEN_TYPE, authType); + try { + pendingIntent.send(context, 0, responseIntent); + } catch (CanceledException exception) { + logger.warning("Canceled auth request: %s", exception); + } + } + + // Prevent instantiation. + private AndroidListenerIntents() { + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerManifest.xml b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerManifest.xml new file mode 100644 index 0000000..3783df2 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerManifest.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- Copyright 2011 Google Inc. All Rights Reserved. --> + <!-- Manifest for AndroidListener. Must be merged with + j/c/g/ipc/invalidation/external/client/android2/AndroidManifest.xml. --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.ipc.invalidation.external.client.contrib"> + <application> + <!-- Receiver for scheduler alarms. --> + <receiver android:exported="false" + android:name="com.google.ipc.invalidation.external.client.contrib.AndroidListener$AlarmReceiver"/> + </application> +</manifest> diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerProtos.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerProtos.java new file mode 100644 index 0000000..c5b077e --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerProtos.java @@ -0,0 +1,112 @@ +/* + * 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.external.client.contrib; + +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.ProtoWrapperConverter; +import com.google.ipc.invalidation.ticl.TiclExponentialBackoffDelayGenerator; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.RegistrationCommand; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.StartCommand; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.util.Bytes; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Static helper class supporting construction of valid {code AndroidListenerProtocol} messages. + * + */ +class AndroidListenerProtos { + + /** Creates a retry register command for the given object and client. */ + static RegistrationCommand newDelayedRegisterCommand(Bytes clientId, ObjectId objectId) { + final boolean isRegister = true; + return newDelayedRegistrationCommand(clientId, objectId, isRegister); + } + + /** Creates a retry unregister command for the given object and client. */ + static RegistrationCommand newDelayedUnregisterCommand(Bytes clientId, ObjectId objectId) { + final boolean isRegister = false; + return newDelayedRegistrationCommand(clientId, objectId, isRegister); + } + + /** Creates proto for {@link AndroidListener} state. */ + static AndroidListenerState newAndroidListenerState(Bytes clientId, int requestCodeSeqNum, + Map<ObjectId, TiclExponentialBackoffDelayGenerator> delayGenerators, + Collection<ObjectId> desiredRegistrations) { + ArrayList<RetryRegistrationState> retryRegistrationState = + new ArrayList<RetryRegistrationState>(delayGenerators.size()); + for (Entry<ObjectId, TiclExponentialBackoffDelayGenerator> entry : delayGenerators.entrySet()) { + retryRegistrationState.add( + newRetryRegistrationState(entry.getKey(), entry.getValue())); + } + return AndroidListenerState.create( + ProtoWrapperConverter.convertToObjectIdProtoCollection(desiredRegistrations), + retryRegistrationState, clientId, requestCodeSeqNum); + } + + /** Creates proto for retry registration state. */ + static RetryRegistrationState newRetryRegistrationState(ObjectId objectId, + TiclExponentialBackoffDelayGenerator delayGenerator) { + return RetryRegistrationState.create(ProtoWrapperConverter.convertToObjectIdProto(objectId), + delayGenerator.marshal()); + } + + /** Returns {@code true} iff the given proto is valid. */ + static boolean isValidAndroidListenerState(AndroidListenerState state) { + return state.hasClientId() && state.hasRequestCodeSeqNum(); + } + + /** Returns {@code true} iff the given proto is valid. */ + static boolean isValidRegistrationCommand(RegistrationCommand command) { + return command.hasIsRegister() && command.hasClientId() && command.hasIsDelayed(); + } + + /** Returns {@code true} iff the given proto is valid. */ + static boolean isValidStartCommand(StartCommand command) { + return command.hasClientType() && command.hasClientName(); + } + + /** Creates start command proto. */ + static StartCommand newStartCommand(int clientType, Bytes clientName, + boolean allowSuppression) { + return StartCommand.create(clientType, clientName, allowSuppression); + } + + static RegistrationCommand newRegistrationCommand(Bytes clientId, + Iterable<ObjectId> objectIds, boolean isRegister) { + return RegistrationCommand.create(isRegister, + ProtoWrapperConverter.convertToObjectIdProtoCollection(objectIds), clientId, + /* isDelayed */ false); + } + + private static RegistrationCommand newDelayedRegistrationCommand(Bytes clientId, + ObjectId objectId, boolean isRegister) { + List<ObjectIdP> objectIds = new ArrayList<ObjectIdP>(1); + objectIds.add(ProtoWrapperConverter.convertToObjectIdProto(objectId)); + return RegistrationCommand.create(isRegister, objectIds, clientId, /* isDelayed */ true); + } + + // Prevent instantiation. + private AndroidListenerProtos() { + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java new file mode 100644 index 0000000..0b0bf52 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java @@ -0,0 +1,304 @@ +/* + * 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.external.client.contrib; + +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.ProtoWrapperConverter; +import com.google.ipc.invalidation.ticl.TiclExponentialBackoffDelayGenerator; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol; +import com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState; +import com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.Marshallable; +import com.google.ipc.invalidation.util.TypedUtil; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; +import java.util.UUID; + + +/** + * Encapsulates state to simplify persistence and tracking of changes. Internally maintains an + * {@link #isDirty} bit. Call {@link #resetIsDirty} to indicate that changes have been persisted. + * + * <p>Notes on the {@link #desiredRegistrations} (DR) and {@link #delayGenerators} (DG) collections: + * When the client application registers for an object, it is immediately added to DR. Similarly, + * an object is removed from DR when the application unregisters. If a registration failure is + * reported, the object is removed from DR if it exists and a delay generator is added to DG if one + * does not already exist. (In the face of a failure, we assume that the registration is not desired + * by the application unless/until the application retries.) When there is a successful + * registration, the corresponding DG entry is removed. There are two independent collections rather + * than one since we may be applying exponential backoff for an object when it is not in DR, and we + * may have no reason to delay operations against an object in DR as well. + * + * <p>By removing objects from the {@link #desiredRegistrations} collection on failures, we are + * essentially assuming that the client application doesn't care about the registration until we're + * told otherwise -- by a subsequent call to register or unregister. + * + */ +final class AndroidListenerState + implements Marshallable<AndroidListenerProtocol.AndroidListenerState> { + + /** + * Exponential backoff delay generators used to determine delay before registration retries. + * There is a delay generator for every failing object. + */ + private final Map<ObjectId, TiclExponentialBackoffDelayGenerator> delayGenerators = + new HashMap<ObjectId, TiclExponentialBackoffDelayGenerator>(); + + /** The set of registrations for which the client wants to be registered. */ + private final Set<ObjectId> desiredRegistrations; + + /** Random generator used for all delay generators. */ + private final Random random = new Random(); + + /** Initial maximum retry delay for exponential backoff. */ + private final int initialMaxDelayMs; + + /** Maximum delay factor for exponential backoff (relative to {@link #initialMaxDelayMs}). */ + private final int maxDelayFactor; + + /** Sequence number for alarm manager request codes. */ + private int requestCodeSeqNum; + + /** + * Dirty flag. {@code true} whenever changes are made, reset to false when {@link #resetIsDirty} + * is called. State initialized from a proto is assumed to be initially clean. + */ + private boolean isDirty; + + /** + * The identifier for the current client. The ID is randomly generated and is used to ensure that + * messages are not handled by the wrong client instance. + */ + private final Bytes clientId; + + /** Initializes state for a new client. */ + AndroidListenerState(int initialMaxDelayMs, int maxDelayFactor) { + desiredRegistrations = new HashSet<ObjectId>(); + clientId = createGloballyUniqueClientId(); + // Assigning a client ID dirties the state because calling the constructor twice produces + // different results. + isDirty = true; + requestCodeSeqNum = 0; + this.initialMaxDelayMs = initialMaxDelayMs; + this.maxDelayFactor = maxDelayFactor; + } + + /** Initializes state from proto. */ + AndroidListenerState(int initialMaxDelayMs, int maxDelayFactor, + AndroidListenerProtocol.AndroidListenerState state) { + desiredRegistrations = new HashSet<ObjectId>(); + for (ObjectIdP objectIdProto : state.getRegistration()) { + desiredRegistrations.add(ProtoWrapperConverter.convertFromObjectIdProto(objectIdProto)); + } + for (RetryRegistrationState retryState : state.getRetryRegistrationState()) { + ObjectIdP objectIdP = retryState.getNullableObjectId(); + if (objectIdP == null) { + continue; + } + ObjectId objectId = ProtoWrapperConverter.convertFromObjectIdProto(objectIdP); + delayGenerators.put(objectId, new TiclExponentialBackoffDelayGenerator(random, + initialMaxDelayMs, maxDelayFactor, retryState.getExponentialBackoffState())); + } + clientId = state.getClientId(); + requestCodeSeqNum = state.getRequestCodeSeqNum(); + isDirty = false; + this.initialMaxDelayMs = initialMaxDelayMs; + this.maxDelayFactor = maxDelayFactor; + } + + /** Increments and returns sequence number for alarm manager request codes. */ + int getNextRequestCode() { + isDirty = true; + return ++requestCodeSeqNum; + } + + /** + * See specs for {@link TiclExponentialBackoffDelayGenerator#getNextDelay}. Gets next delay for + * the given {@code objectId}. If a delay generator does not yet exist for the object, one is + * created. + */ + int getNextDelay(ObjectId objectId) { + TiclExponentialBackoffDelayGenerator delayGenerator = + delayGenerators.get(objectId); + if (delayGenerator == null) { + delayGenerator = new TiclExponentialBackoffDelayGenerator(random, initialMaxDelayMs, + maxDelayFactor); + delayGenerators.put(objectId, delayGenerator); + } + // Requesting a delay from a delay generator modifies its internal state. + isDirty = true; + return delayGenerator.getNextDelay(); + } + + /** Inform that there has been a successful registration for an object. */ + void informRegistrationSuccess(ObjectId objectId) { + // Since registration was successful, we can remove exponential backoff (if any) for the given + // object. + resetDelayGeneratorFor(objectId); + } + + /** + * Inform that there has been a registration failure. + * + * <p>Remove the object from the desired registrations collection whenever there's a failure. We + * don't care if the op that failed was actually an unregister because we never suppress an + * unregister request (even if the object is not in the collection). See + * {@link AndroidListener#issueRegistration}. + */ + public void informRegistrationFailure(ObjectId objectId, boolean isTransient) { + removeDesiredRegistration(objectId); + if (!isTransient) { + // There should be no retries for the object, so remove any backoff state associated with it. + resetDelayGeneratorFor(objectId); + } + } + + /** + * If there is a backoff delay generator for the given object, removes it and sets dirty flag. + */ + private void resetDelayGeneratorFor(ObjectId objectId) { + if (TypedUtil.remove(delayGenerators, objectId) != null) { + isDirty = true; + } + } + + /** Adds the given registration. Returns {@code true} if it was not already tracked. */ + boolean addDesiredRegistration(ObjectId objectId) { + if (desiredRegistrations.add(objectId)) { + isDirty = true; + return true; + } + return false; + } + + /** Removes the given registration. Returns {@code true} if it was actually tracked. */ + boolean removeDesiredRegistration(ObjectId objectId) { + if (desiredRegistrations.remove(objectId)) { + isDirty = true; + return true; + } + return false; + } + + /** + * Resets the {@link #isDirty} flag to {@code false}. Call after marshalling and persisting state. + */ + void resetIsDirty() { + isDirty = false; + } + + @Override + public AndroidListenerProtocol.AndroidListenerState marshal() { + return AndroidListenerProtos.newAndroidListenerState(clientId, requestCodeSeqNum, + delayGenerators, desiredRegistrations); + } + + /** + * Gets the identifier for the current client. Used to determine if registrations commands are + * relevant to this instance. + */ + Bytes getClientId() { + return clientId; + } + + /** Returns {@code true} iff registration is desired for the given object. */ + boolean containsDesiredRegistration(ObjectId objectId) { + return TypedUtil.contains(desiredRegistrations, objectId); + } + + /** + * Returns {@code true} if changes have been made since the last successful call to + * {@link #resetIsDirty}. + */ + boolean getIsDirty() { + return isDirty; + } + + @Override + public int hashCode() { + // Since the client ID is globally unique, it's sufficient as a hashCode. + return clientId.hashCode(); + } + + /** + * Overridden for tests which compare listener states to verify that they have been correctly + * (un)marshalled. We implement equals rather than exposing private data. + */ + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (!(object instanceof AndroidListenerState)) { + return false; + } + + AndroidListenerState that = (AndroidListenerState) object; + + return (this.isDirty == that.isDirty) + && (this.requestCodeSeqNum == that.requestCodeSeqNum) + && (this.desiredRegistrations.size() == that.desiredRegistrations.size()) + && (this.desiredRegistrations.containsAll(that.desiredRegistrations)) + && TypedUtil.<Bytes>equals(this.clientId, that.clientId) + && equals(this.delayGenerators, that.delayGenerators); + } + + /** Compares the contents of two {@link #delayGenerators} maps. */ + private static boolean equals(Map<ObjectId, TiclExponentialBackoffDelayGenerator> x, + Map<ObjectId, TiclExponentialBackoffDelayGenerator> y) { + if (x.size() != y.size()) { + return false; + } + for (Entry<ObjectId, TiclExponentialBackoffDelayGenerator> xEntry : x.entrySet()) { + TiclExponentialBackoffDelayGenerator yGenerator = y.get(xEntry.getKey()); + if ((yGenerator == null) || !TypedUtil.<ExponentialBackoffState>equals( + xEntry.getValue().marshal(), yGenerator.marshal())) { + return false; + } + } + return true; + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "AndroidListenerState[%s]: isDirty = %b, " + + "desiredRegistrations.size() = %d, delayGenerators.size() = %d, requestCodeSeqNum = %d", + clientId, isDirty, desiredRegistrations.size(), delayGenerators.size(), requestCodeSeqNum); + } + + /** + * Constructs a new globally unique ID for the client. Can be used to determine if commands + * originated from this instance of the listener. + */ + private static Bytes createGloballyUniqueClientId() { + UUID guid = UUID.randomUUID(); + byte[] bytes = new byte[16]; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.putLong(guid.getLeastSignificantBits()); + buffer.putLong(guid.getMostSignificantBits()); + return new Bytes(bytes); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/MultiplexingGcmListener.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/MultiplexingGcmListener.java new file mode 100644 index 0000000..10cb3ce --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/MultiplexingGcmListener.java @@ -0,0 +1,359 @@ +/* + * 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.external.client.contrib; + +import com.google.android.gcm.GCMBaseIntentService; +import com.google.android.gcm.GCMBroadcastReceiver; +import com.google.android.gcm.GCMRegistrar; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.ticl.android2.WakeLockManager; + +import android.app.IntentService; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ServiceInfo; + +/** + * A Google Cloud Messaging listener class that rebroadcasts events as package-scoped + * broadcasts. This allows multiple components to share a single GCM connection. + * <p> + * This listener uses an API of broadcasted Intents that is modeled after that provided by + * {@link GCMBaseIntentService}. For each upcall (e.g., onMessage, on Registered, etc) specified + * by {@code GCMBaseIntentService}, there is an {@code EXTRA_OP_...} constant defined in + * {@link Intents}. + * <p> + * Note that this class does <b>NOT</b> handle registering with GCM; applications are still required + * to do that in the usual way (e.g., using the GCMRegistrar class from the GCM library). + * <p> + * In order to raise a {@code GCMBaseIntentService} event to listeners, this service will broadcast + * an Intent with the following properties: + * 1. The action of the Intent is {@link Intents#ACTION} + * 2. There is a boolean-valued extra in the Intent whose key is the {@code EXTRA_OP_...} key + * for that call and whose value is {@code true}. For any intent, exactly one {@code EXTRA_OP} + * extra will be set. + * 3. The Intent contains additional call-specific extras required to interpret it. (See note for + * onMessage, below). + * <p> + * Clients of this service <b>MUST NOT</b> assume that there is a one-to-one mapping between + * issued broadcasts and actual GCM intents. I.e., this service may issue broadcast intents + * spontaneously, and it may not issue an intent for every GCM event. + * <p> + * For the onMessage() call, the broadcast intent will contain key/value extras containing the + * message payload. These extras are guaranteed to be identical to those that would have been in + * the Intent provided to the onMessage call. However, clients <b>MUST NOT</b> assume that the + * Intent broadcast to communicate a GCM message is literally the same Intent generated by the GCM + * client library. + * <p> + * This class does not expose the {@code onError} call, since according to the GCM documentation + * there is nothing to do except log an error (which this class does). + * + */ +public class MultiplexingGcmListener extends GCMBaseIntentService { + /* This class is public so that it can be instantiated by the Android runtime. */ + + /** Constants used in broadcast Intents. */ + public static final class Intents { + /** Prefix of the action and extras. */ + private static final String PREFIX = "com.google.ipc.invalidation.gcmmplex."; + + /** Action of all broadcast intents issued. */ + public static final String ACTION = PREFIX + "EVENT"; + + /** Extra corresponding to an {@code onMessage} upcall. */ + public static final String EXTRA_OP_MESSAGE = PREFIX + "MESSAGE"; + + /** Extra corresponding to an {@code onRegistered} upcall. */ + public static final String EXTRA_OP_REGISTERED = PREFIX + "REGISTERED"; + + /** Extra corresponding to an {@code onUnregistered} upcall. */ + public static final String EXTRA_OP_UNREGISTERED = PREFIX + "UNREGISTERED"; + + /** Extra corresponding to an {@code onDeletedMessages} upcall. */ + public static final String EXTRA_OP_DELETED_MESSAGES = PREFIX + "DELETED_MSGS"; + + /** + * Extra set iff the operation is {@link #EXTRA_OP_REGISTERED} or + * {@link #EXTRA_OP_UNREGISTERED}; it is string-valued and holds the registration id. + */ + public static final String EXTRA_DATA_REG_ID = PREFIX + "REGID"; + + /** + * Extra set iff the operation is {@link #EXTRA_OP_DELETED_MESSAGES}; it is integer-valued + * and holds the number of deleted messages. + */ + public static final String EXTRA_DATA_NUM_DELETED_MSGS = PREFIX + "NUM_DELETED_MSGS"; + } + + /** + * {@link GCMBroadcastReceiver} that forwards GCM intents to the {@code MultiplexingGcmListener} + * class. + */ + public static class GCMReceiver extends GCMBroadcastReceiver { + /* This class is public so that it can be instantiated by the Android runtime. */ + @Override + protected String getGCMIntentServiceClassName(Context context) { + return MultiplexingGcmListener.class.getName(); + } + } + + /** + * Convenience base class for client implementations. It provides base classes for a broadcast + * receiver and an intent service that work together to handle events from the + * {@code MultiplexingGcmListener} while holding a wake lock. + * <p> + * This class guarantees that the {@code onYYY} methods will be called holding a wakelock, and + * that the wakelock will be automatically released when the method returns. + * <p> + * The wakelock will also be automatically released + * {@link Receiver#WAKELOCK_TIMEOUT_MS} ms after the original Intent was received by the + * {@link Receiver} class, to guard against leaks. Applications requiring a longer-duration + * wakelock should acquire one on their own in the appropriate {@code onYYY} method. + */ + public static abstract class AbstractListener extends IntentService { + /** Prefix of all wakelocks acquired by the receiver and the intent service. */ + private static final String WAKELOCK_PREFIX = "multiplexing-gcm-listener:"; + + /** Intent extra key used to hold wakelock names, for runtime checks. */ + private static final String EXTRA_WAKELOCK_NAME = + "com.google.ipc.invalidation.gcmmplex.listener.WAKELOCK_NAME"; + + /** + * A {@code BroadcastReceiver} to receive intents from the {@code MultiplexingGcmListener} + * service. It acquires a wakelock and forwards the intent to the service named by + * {@link #getServiceClass}, which must be a subclass of {@code AbstractListener}. + */ + public static abstract class Receiver extends BroadcastReceiver { + /** Timeout after which wakelocks will be automatically released. */ + private static final int WAKELOCK_TIMEOUT_MS = 30 * 1000; + + @Override + public final void onReceive(Context context, Intent intent) { + // This method is final to prevent subclasses from overriding it and introducing errors in + // the wakelock protocol. + Class<?> serviceClass = getServiceClass(); + + // If the service isn't an AbstractListener subclass, then it will not release the wakelock + // properly, causing bugs. + if (!AbstractListener.class.isAssignableFrom(serviceClass)) { + throw new RuntimeException( + "Service class is not a subclass of AbstractListener: " + serviceClass); + } + String wakelockKey = getWakelockKey(serviceClass); + intent.setClass(context, serviceClass); + + // To avoid insidious bugs, tell the service which wakelock we acquired. The service will + // log a warning if the lock it releases is not this lock. + intent.putExtra(EXTRA_WAKELOCK_NAME, wakelockKey); + + // Acquire the lock and start the service. The service is responsible for releasing the + // lock. + WakeLockManager.getInstance(context).acquire(wakelockKey, WAKELOCK_TIMEOUT_MS); + context.startService(intent); + } + + /** Returns the class of the service that will handle intents. */ + protected abstract Class<?> getServiceClass(); + } + + protected AbstractListener(String name) { + super(name); + + // If the process dies during a call to onHandleIntent, redeliver the intent when the service + // restarts. + setIntentRedelivery(true); + } + + @Override + public final void onHandleIntent(Intent intent) { + if (intent == null) { + return; + } + + // This method is final to prevent subclasses from overriding it and introducing errors in + // the wakelock protocol. + try { + doHandleIntent(intent); + } finally { + // Release the wakelock acquired by the receiver. The receiver provides the name of the + // lock it acquired in the Intent so that we can sanity-check that we are releasing the + // right lock. + String receiverAcquiredWakelock = intent.getStringExtra(EXTRA_WAKELOCK_NAME); + String wakelockToRelease = getWakelockKey(getClass()); + if (!wakelockToRelease.equals(receiverAcquiredWakelock)) { + logger.warning("Receiver acquired wakelock '%s' but releasing '%s'", + receiverAcquiredWakelock, wakelockToRelease); + } + WakeLockManager wakelockManager = WakeLockManager.getInstance(this); + wakelockManager.release(wakelockToRelease); + } + } + + /** Handles {@code intent} while holding a wake lock. */ + private void doHandleIntent(Intent intent) { + // Ensure this is an Intent we want to handle. + if (!MultiplexingGcmListener.Intents.ACTION.equals(intent.getAction())) { + logger.warning("Ignoring intent with unknown action: %s", intent); + return; + } + // Dispatch based on the extras. + if (intent.hasExtra(MultiplexingGcmListener.Intents.EXTRA_OP_MESSAGE)) { + onMessage(intent); + } else if (intent.hasExtra(MultiplexingGcmListener.Intents.EXTRA_OP_REGISTERED)) { + onRegistered(intent.getStringExtra(MultiplexingGcmListener.Intents.EXTRA_DATA_REG_ID)); + } else if (intent.hasExtra(MultiplexingGcmListener.Intents.EXTRA_OP_UNREGISTERED)) { + onUnregistered(intent.getStringExtra(MultiplexingGcmListener.Intents.EXTRA_DATA_REG_ID)); + } else if (intent.hasExtra(MultiplexingGcmListener.Intents.EXTRA_OP_DELETED_MESSAGES)) { + int numDeleted = + intent.getIntExtra(MultiplexingGcmListener.Intents.EXTRA_DATA_NUM_DELETED_MSGS, -1); + if (numDeleted == -1) { + logger.warning("Could not parse num-deleted field of GCM broadcast: %s", intent); + return; + } + onDeletedMessages(numDeleted); + } else { + logger.warning("Broadcast GCM intent with no known operation: %s", intent); + } + } + + // These methods have the same specs as in {@code GCMBaseIntentService}. + protected abstract void onMessage(Intent intent); + protected abstract void onRegistered(String registrationId); + protected abstract void onUnregistered(String registrationId); + protected abstract void onDeletedMessages(int total); + + /** + * Returns the name of the wakelock to acquire for the intent service implemented by + * {@code clazz}. + */ + private static String getWakelockKey(Class<?> clazz) { + return WAKELOCK_PREFIX + clazz.getName(); + } + } + + /** + * Name of the metadata element within the {@code service} element whose value is a + * comma-delimited list of GCM sender ids. + */ + private static final String GCM_SENDER_IDS_METADATA_KEY = "sender_ids"; + + /** Logger. */ + private static final Logger logger = AndroidLogger.forTag("MplexGcmListener"); + + // All onYYY methods work by constructing an appropriate Intent and broadcasting it. + + @Override + protected void onMessage(Context context, Intent intent) { + Intent newIntent = new Intent(); + newIntent.putExtra(Intents.EXTRA_OP_MESSAGE, true); + + // Copy the extra keys containing the message payload into the new Intent. + for (String extraKey : intent.getExtras().keySet()) { + newIntent.putExtra(extraKey, intent.getStringExtra(extraKey)); + } + rebroadcast(newIntent); + } + + @Override + protected void onRegistered(Context context, String registrationId) { + Intent intent = new Intent(); + intent.putExtra(Intents.EXTRA_OP_REGISTERED, true); + intent.putExtra(Intents.EXTRA_DATA_REG_ID, registrationId); + rebroadcast(intent); + } + + @Override + protected void onUnregistered(Context context, String registrationId) { + Intent intent = new Intent(); + intent.putExtra(Intents.EXTRA_OP_UNREGISTERED, true); + intent.putExtra(Intents.EXTRA_DATA_REG_ID, registrationId); + rebroadcast(intent); + } + + @Override + protected void onDeletedMessages(Context context, int total) { + Intent intent = new Intent(); + intent.putExtra(Intents.EXTRA_OP_DELETED_MESSAGES, true); + intent.putExtra(Intents.EXTRA_DATA_NUM_DELETED_MSGS, total); + rebroadcast(intent); + } + + @Override + protected void onError(Context context, String errorId) { + // This is called for unrecoverable errors, so just log a warning. + logger.warning("GCM error: %s", errorId); + } + + @Override + protected String[] getSenderIds(Context context) { + return readSenderIdsFromManifestOrDie(this); + } + + /** + * Broadcasts {@code intent} with the action set to {@link Intents#ACTION} and the package name + * set to the package name of this service. + */ + private void rebroadcast(Intent intent) { + intent.setAction(Intents.ACTION); + intent.setPackage(getPackageName()); + sendBroadcast(intent); + } + + /** + * Registers with GCM if not already registered. Also verifies that the device supports GCM + * and that the manifest is correctly configured. Returns the existing registration id, if one + * exists, or the empty string if one does not. + * + * @throws UnsupportedOperationException if the device does not have all GCM dependencies + * @throws IllegalStateException if the manifest is not correctly configured + */ + public static String initializeGcm(Context context) { + GCMRegistrar.checkDevice(context); + GCMRegistrar.checkManifest(context); + final String regId = GCMRegistrar.getRegistrationId(context); + if (regId.isEmpty()) { + GCMRegistrar.register(context, readSenderIdsFromManifestOrDie(context)); + } + return regId; + } + + /** + * Returns the GCM sender ids from {@link #GCM_SENDER_IDS_METADATA_KEY} or throws a + * {@code RuntimeException} if they are not defined. + */ + + static String[] readSenderIdsFromManifestOrDie(Context context) { + try { + ServiceInfo serviceInfo = context.getPackageManager().getServiceInfo( + new ComponentName(context, MultiplexingGcmListener.class), PackageManager.GET_META_DATA); + if (serviceInfo.metaData == null) { + throw new RuntimeException("Service has no metadata"); + } + String senderIds = serviceInfo.metaData.getString(GCM_SENDER_IDS_METADATA_KEY); + if (senderIds == null) { + throw new RuntimeException("Service does not have the sender-ids metadata"); + } + return senderIds.split(","); + } catch (NameNotFoundException exception) { + throw new RuntimeException("Could not read service info from manifest", exception); + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/AckHandle.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/AckHandle.java new file mode 100644 index 0000000..67d5e08 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/AckHandle.java @@ -0,0 +1,68 @@ +/* + * 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.external.client.types; + +import java.util.Arrays; + +/** + * Represents an opaque handle that can be used to acknowledge an invalidation event by calling + * {@code InvalidationClient.acknowledge(AckHandle)} to indicate that the client has successfully + * handled the event. + * + */ +public final class AckHandle { + + /** The serialized representation of the handle */ + private final byte[] handleData; + + /** Creates a new ack handle from the serialized {@code handleData} representation. */ + public static AckHandle newInstance(byte[] handleData) { + return new AckHandle(handleData); + } + + private AckHandle(byte[] handleData) { + this.handleData = handleData; + } + + public byte[] getHandleData() { + return handleData; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (!(object instanceof AckHandle)) { + return false; + } + + final AckHandle other = (AckHandle) object; + return Arrays.equals(handleData, other.handleData); + } + + @Override + public int hashCode() { + return Arrays.hashCode(handleData); + } + + @Override + public String toString() { + return "AckHandle: " + BytesFormatter.toString(handleData); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ApplicationClientId.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ApplicationClientId.java new file mode 100644 index 0000000..fa3cdbc --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ApplicationClientId.java @@ -0,0 +1,77 @@ +/* + * 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.external.client.types; + +import com.google.ipc.invalidation.util.Preconditions; + +import java.util.Arrays; + +/** + * An identifier for application clients in an application-defined way. I.e., a client name in an + * application naming scheme. This is not interpreted by the invalidation system - however, it is + * used opaquely to squelch invalidations for the cient causing an update, e.g., if a client C + * whose app client id is C.appClientId changes object X and the backend store informs the backend + * invalidation sytsem that X was modified by X.appClientId, the invalidation to C can then be + * squelched by the invalidation system. + * + */ +public final class ApplicationClientId { + + /** The opaque id of the client application. */ + private final byte[] clientName; + + /** + * Creates an application client id for the given {@code clientName} (does not make a copy of the + * byte array). + */ + public static ApplicationClientId newInstance(byte[] appClientId) { + return new ApplicationClientId(appClientId); + } + + /** Creates an application id for the given {@code clientName}. */ + private ApplicationClientId(byte[] clientName) { + this.clientName = Preconditions.checkNotNull(clientName, "clientName"); + } + + public byte[] getClientName() { + return clientName; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (!(object instanceof ApplicationClientId)) { + return false; + } + + final ApplicationClientId other = (ApplicationClientId) object; + return Arrays.equals(clientName, other.clientName); + } + + @Override + public int hashCode() { + return Arrays.hashCode(clientName); + } + + @Override + public String toString() { + return "AppClientId: <, " + BytesFormatter.toString(clientName) + ">"; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/BytesFormatter.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/BytesFormatter.java new file mode 100644 index 0000000..9552cc9 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/BytesFormatter.java @@ -0,0 +1,101 @@ +/* + * 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.external.client.types; + + +/** + * A utility class to format bytes to string for ease of reading and debugging. + * + */ +class BytesFormatter { + + /** + * Three arrays that store the representation of each character from 0 to 255. + * The ith number's octal representation is: CHAR_OCTAL_STRINGS1[i], + * CHAR_OCTAL_STRINGS2[i], CHAR_OCTAL_STRINGS3[i] + * <p> + * E.g., if the number 128, these arrays contain 2, 0, 0 at index 128. We use + * 3 char arrays instead of an array of strings since the code path for a + * character append operation is quite a bit shorter than the append operation + * for strings. + */ + private static final char[] CHAR_OCTAL_STRINGS1 = new char[256]; + private static final char[] CHAR_OCTAL_STRINGS2 = new char[256]; + private static final char[] CHAR_OCTAL_STRINGS3 = new char[256]; + + static { + // Initialize the array with the Octal string values so that we do not have + // to do String.format for every byte during runtime. + for (int i = 0; i < CHAR_OCTAL_STRINGS1.length; i++) { + // Unsophisticated way to get an octal string padded to 3 characters. + String intAsStr = Integer.toOctalString(i); + switch (intAsStr.length()) { + case 3: + break; + case 2: + intAsStr = "0" + intAsStr; + break; + case 1: + intAsStr = "00" + intAsStr; + break; + default: + throw new RuntimeException("Bad integer value: " + intAsStr); + } + if (intAsStr.length() != 3) { + throw new RuntimeException("Bad padding: " + intAsStr); + } + String value = '\\' + intAsStr; + CHAR_OCTAL_STRINGS1[i] = value.charAt(1); + CHAR_OCTAL_STRINGS2[i] = value.charAt(2); + CHAR_OCTAL_STRINGS3[i] = value.charAt(3); + } + } + + /** Returns a human-readable string for the contents of {@code bytes}. */ + public static String toString(byte[] bytes) { + if (bytes == null) { + return null; + } + StringBuilder builder = new StringBuilder(3 * bytes.length); + for (byte c : bytes) { + switch(c) { + case '\n': builder.append('\\'); builder.append('n'); break; + case '\r': builder.append('\\'); builder.append('r'); break; + case '\t': builder.append('\\'); builder.append('t'); break; + case '\"': builder.append('\\'); builder.append('"'); break; + case '\\': builder.append('\\'); builder.append('\\'); break; + default: + if ((c >= 32) && (c < 127) && c != '\'') { + builder.append((char) c); + } else { + int byteValue = c; + if (c < 0) { + byteValue = c + 256; + } + builder.append('\\'); + builder.append(CHAR_OCTAL_STRINGS1[byteValue]); + builder.append(CHAR_OCTAL_STRINGS2[byteValue]); + builder.append(CHAR_OCTAL_STRINGS3[byteValue]); + } + } + } + return builder.toString(); + } + + private BytesFormatter() { // To prevent instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Callback.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Callback.java new file mode 100644 index 0000000..a1296cdde --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Callback.java @@ -0,0 +1,33 @@ +/* + * 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.external.client.types; + + +/** + * An interface to receive objects of a single type. + * + * @param <T> type of object received in the callback + * + */ +public interface Callback<T> { + /** + * Accepts the object provided by the caller. + * + * @param object received object + */ + void accept(T object); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ErrorContext.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ErrorContext.java new file mode 100644 index 0000000..e50d38c --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ErrorContext.java @@ -0,0 +1,38 @@ +/* + * 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.external.client.types; + +/** + * Extra information about the error in {@code ErrorInfo} - cast to appropriate subtype as + * specified for the given reason. + * + */ +public class ErrorContext { + + /** A context with numeric data. */ + public static class NumberContext extends ErrorContext { + private int number; + + public NumberContext(int number) { + this.number = number; + } + + int getNumber() { + return number; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ErrorInfo.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ErrorInfo.java new file mode 100644 index 0000000..68b78b8 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ErrorInfo.java @@ -0,0 +1,93 @@ +/* + * 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.external.client.types; + +/** + * Information about an error given to the application. + * + */ +public final class ErrorInfo { + /** + * Possible reasons for error in {@code InvalidationListener.informError}. The application writer + * must NOT assume that this is complete list since error codes may be added later. That is, for + * error codes that it cannot handle, it should not necessarily just crash the code. It may want + * to present a dialog box to the user (say). For each ErrorReason, the ErrorInfo object has a + * context object. We describe the type and meaning of the context for each named constant below. + */ + public static class ErrorReason { + /** The provided authentication/authorization token is not valid for use. */ + public static final int AUTH_FAILURE = 1; + + /** An unknown failure - more human-readable information is in the error message. */ + public static final int UNKNOWN_FAILURE = -1; + + private ErrorReason() {} // not instantiable + } + + /** The cause of the failure. */ + private final int errorReason; + + /** + * Is the error transient or permanent. See discussion in {@code Status.Code} for permanent and + * transient failure handling. + */ + private final boolean isTransient; + + /** Human-readable description of the error. */ + private final String errorMessage; + + /** Extra information about the error - cast to appropriate object as specified by the reason. */ + private final Object context; + + /** + * Returns a new ErrorInfo object given the reason for the error, whether it is transient or + * permanent, a helpful error message and extra context about the error. + */ + public static ErrorInfo newInstance(int errorReason, boolean isTransient, + String errorMessage, ErrorContext context) { + return new ErrorInfo(errorReason, isTransient, errorMessage, context); + } + + private ErrorInfo(int errorReason, boolean isTransient, String errorMessage, + ErrorContext context) { + this.errorReason = errorReason; + this.isTransient = isTransient; + this.errorMessage = errorMessage; + this.context = context; + } + + public boolean isTransient() { + return isTransient; + } + + public String getErrorMessage() { + return errorMessage; + } + + public int getErrorReason() { + return errorReason; + } + + public Object getContext() { + return context; + } + + @Override + public String toString() { + return "ErrorInfo: " + errorReason + ", " + isTransient + ", " + errorMessage + ", " + context; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Invalidation.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Invalidation.java new file mode 100644 index 0000000..acfe952 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Invalidation.java @@ -0,0 +1,136 @@ +/* + * 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.external.client.types; + +import com.google.ipc.invalidation.util.Preconditions; + +import java.util.Arrays; + + +/** + * A class to represent an invalidation for a given object/version and an optional payload. + * + */ +public final class Invalidation { + + /** The object being invalidated/updated. */ + private final ObjectId objectId; + + /** The new version of the object. */ + private final long version; + + /** Optional payload for the object. */ + private final byte[] payload; + + /** Whether this is a restarted invalidation, for internal use only. */ + private final boolean isTrickleRestart; + + /** + * Creates an invalidation for the given {@code object} and {@code version}. + */ + public static Invalidation newInstance(ObjectId objectId, long version) { + return new Invalidation(objectId, version, null, true); + } + + /** + * Creates an invalidation for the given {@code object} and {@code version} and {@code payload} + */ + public static Invalidation newInstance(ObjectId objectId, long version, + byte[] payload) { + return new Invalidation(objectId, version, payload, true); + } + + /** + * Creates an invalidation for the given {@code object}, {@code version} and optional + * {@code payload} and internal {@code isTrickleRestart} flag. + */ + public static Invalidation newInstance(ObjectId objectId, long version, + byte[] payload, boolean isTrickleRestart) { + return new Invalidation(objectId, version, payload, isTrickleRestart); + } + + /** + * Creates an invalidation for the given {@code object}, {@code version} and optional + * {@code payload} and optional {@code componentStampLog}. + */ + private Invalidation(ObjectId objectId, long version, byte[] payload, + boolean isTrickleRestart) { + this.objectId = Preconditions.checkNotNull(objectId, "objectId"); + this.version = version; + this.payload = payload; + this.isTrickleRestart = isTrickleRestart; + } + + public ObjectId getObjectId() { + return objectId; + } + + public long getVersion() { + return version; + } + + /** Returns the optional payload for the object - if none exists, returns {@code null}. */ + public byte[] getPayload() { + return payload; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (!(object instanceof Invalidation)) { + return false; + } + + final Invalidation other = (Invalidation) object; + if ((payload != null) != (other.payload != null)) { + // One of the objects has a payload and the other one does not. + return false; + } + // Both have a payload or not. + return objectId.equals(other.objectId) && (version == other.version) && + (isTrickleRestart == other.isTrickleRestart) && + ((payload == null) || Arrays.equals(payload, other.payload)); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + objectId.hashCode(); + result = 31 * result + (int) (version ^ (version >>> 32)); + + // Booleans.hashCode() inlined here to reduce client library size. + result = 31 * result + (isTrickleRestart ? 1231 : 1237); + if (payload != null) { + result = 31 * result + Arrays.hashCode(payload); + } + return result; + } + + @Override + public String toString() { + return "Inv: <" + objectId + ", " + version + ", " + isTrickleRestart + ", " + + BytesFormatter.toString(payload) + ">"; + } + + /** Returns whether this is a restarted invalidation, for internal use only. */ + public boolean getIsTrickleRestartForInternalUse() { + return isTrickleRestart; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ObjectId.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ObjectId.java new file mode 100644 index 0000000..2a6217f --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ObjectId.java @@ -0,0 +1,85 @@ +/* + * 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.external.client.types; + +import com.google.ipc.invalidation.util.Preconditions; + +import java.util.Arrays; + +/** + * A class to represent a unique object id that an application can register or + * unregister for. + * + */ +public final class ObjectId { + + /** The invalidation source type. */ + private final int source; + + /** The name/unique id for the object. */ + private final byte[] name; + + /** + * Creates an object id for the given {@code source} and id {@code name} (does not make a copy of + * the array). + */ + public static ObjectId newInstance(int source, byte[] name) { + return new ObjectId(source, name); + } + + /** Creates an object id for the given {@code source} and id {@code name}. */ + private ObjectId(int source, byte[] name) { + Preconditions.checkState(source >= 0, "source"); + this.source = source; + this.name = Preconditions.checkNotNull(name, "name"); + } + + public int getSource() { + return source; + } + + public byte[] getName() { + return name; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (!(object instanceof ObjectId)) { + return false; + } + + final ObjectId other = (ObjectId) object; + if ((source != other.source) || !Arrays.equals(name, other.name)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return source ^ Arrays.hashCode(name); + } + + @Override + public String toString() { + return "Oid: <" + source + ", " + BytesFormatter.toString(name) + ">"; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/SimplePair.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/SimplePair.java new file mode 100644 index 0000000..aa5cb76 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/SimplePair.java @@ -0,0 +1,115 @@ +/* + * 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.external.client.types; + + +/** + * An immutable, semantic-free ordered pair of nullable values. These can be + * accessed using the {@link #getFirst} and {@link #getSecond} methods. Equality + * and hashing are defined in the natural way. + * + * @param <FirstType> The type of the first element + * @param <SecondType> The type of the second element + * + */ +public final class SimplePair<FirstType, SecondType> { + /** + * Creates a new pair containing the given elements in order. + */ + public static <FirstType, SecondType> SimplePair<FirstType, SecondType> of( + FirstType first, SecondType second) { + return new SimplePair<FirstType, SecondType>(first, second); + } + + /** + * The first element of the pair; see also {@link #getFirst}. + */ + public final FirstType first; + + /** + * The second element of the pair; see also {@link #getSecond}. + */ + public final SecondType second; + + /** + * Constructor. It is usually easier to call {@link #of}. + */ + public SimplePair(FirstType first, SecondType second) { + this.first = first; + this.second = second; + } + + /** + * Returns the first element of this pair; see also {@link #first}. + */ + public FirstType getFirst() { + return first; + } + + /** + * Returns the second element of this pair; see also {@link #second}. + */ + public SecondType getSecond() { + return second; + } + + @Override + public boolean equals(Object object) { + if (object instanceof SimplePair<?, ?>) { + SimplePair<?, ?> that = (SimplePair<?, ?>) object; + return areObjectsEqual(this.first, that.first) && areObjectsEqual(this.second, that.second); + } + return false; + } + + /** + * Determines whether two possibly-null objects are equal. Returns: + * + * <ul> + * <li>{@code true} if {@code a} and {@code b} are both null. + * <li>{@code true} if {@code a} and {@code b} are both non-null and they are + * equal according to {@link Object#equals(Object)}. + * <li>{@code false} in all other situations. + * </ul> + * + * <p>This assumes that any non-null objects passed to this function conform + * to the {@code equals()} contract. + */ + private static boolean areObjectsEqual(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + @Override + public int hashCode() { + int hash1 = first == null ? 0 : first.hashCode(); + int hash2 = second == null ? 0 : second.hashCode(); + return 31 * hash1 + hash2; + } + + /** + * {@inheritDoc} + * + * <p>This implementation returns a string in the form + * {@code (first, second)}, where {@code first} and {@code second} are the + * String representations of the first and second elements of this pair, as + * given by {@link String#valueOf(Object)}. Subclasses are free to override + * this behavior. + */ + @Override public String toString() { + return "(" + first + ", " + second + ")"; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Status.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Status.java new file mode 100644 index 0000000..0e6d39a --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Status.java @@ -0,0 +1,122 @@ +/* + * 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.external.client.types; + +/** + * Information given to about a operation - success, temporary or permanent failure. + * + */ +public final class Status { + + /** Actual status of the operation: Whether successful, transient or permanent failure. */ + public enum Code { + /** Operation was successful. */ + SUCCESS, + + /** + * Operation had a transient failure. The application can retry the failed operation later - + * if it chooses to do so, it must use a sensible backoff policy such as exponential backoff. + */ + TRANSIENT_FAILURE, + + /** + * Opration has a permanent failure. Application must not automatically retry without fixing + * the situation (e.g., by presenting a dialog box to the user). + */ + PERMANENT_FAILURE + } + + /** Success or failure. */ + private final Code code; + + /** A message describing why the state was unknown, for debugging. */ + private final String message; + + /** Creates a new Status object given the {@code code} and {@code message}. */ + public static Status newInstance(Status.Code code, String message) { + return new Status(code, message); + } + + private Status(Code code, String message) { + this.code = code; + this.message = message; + } + + /** Returns true iff the status corresponds to a successful completion of the operation.*/ + public boolean isSuccess() { + return code == Code.SUCCESS; + } + + /** + * Whether the failure is transient for an operation, e.g., for a {@code register} operation. + * For transient failures, the application can retry the operation but it must use a sensible + * policy such as exponential backoff so that it does not add significant load to the backend + * servers. + */ + public boolean isTransientFailure() { + return code == Code.TRANSIENT_FAILURE; + } + + /** + * Whether the failure is transient for an operation, e.g., for a {@code register} operation. + * See discussion in {@code Status.Code} for permanent and transient failure handling. + */ + public boolean isPermanentFailure() { + return code == Code.PERMANENT_FAILURE; + } + + /** A message describing why the state was unknown, for debugging. */ + public String getMessage() { + return message; + } + + /** Returns the code for this status message. */ + public Code getCode() { + return code; + } + + @Override + public String toString() { + return "Code: " + code + ", " + message; + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (!(object instanceof Status)) { + return false; + } + + final Status other = (Status) object; + if (code != other.code) { + return false; + + } + if (message == null) { + return other.message == null; + } + return message.equals(other.message); + } + + @Override + public int hashCode() { + return code.hashCode() ^ ((message == null) ? 0 : message.hashCode()); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/AckCache.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/AckCache.java new file mode 100644 index 0000000..207b959 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/AckCache.java @@ -0,0 +1,96 @@ +/* + * 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; + +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.util.TypedUtil; + +import java.util.HashMap; +import java.util.Map; + +/** + * An ack "cache" that allows the TICL to avoid unnecessary delivery of a + * known-version invalidation when the client has aleady acked a known-version, + * restarted invalidation with the same or a greater version number. + * <p> + * This helps invalidation clients avoid unnecessary syncs against their backend + * when invalidations for an object are redelivered or reordered, as can occur + * frequently during a PCR or (to a lesser degree) as a result of internal + * failures and channel flakiness. + * <p> + * This optimization is especially useful for applications that want to use + * the TI Pubsub API to deliver invalidations, because version numbers are not + * a concept in the API itself. While the client could include version numbers + * in the payload, truncation messages do not include a payload. + * <p> + * The cache invalidation API does expose version numbers, so client + * applications could implement the same logic themselves, but many + * do not, so it is a useful convenience to implement this for them in the TICL. + * <p> + * Note this class currently only records acks for restarted, known-version + * invalidations. While we might add ack tracking for continous invalidations at + * some time in the future, tracking continuous invalidations has less of a + * payoff than tracking restarted invalidations, because such an ack does not + * implicitly ack earlier invalidations for that object, and greater complexity, + * because of the potentially unbounded number of acks that need to be tracked + * for each object. + */ +class AckCache { + + /** + * A map from object id to the (long) version number of the highest + * <em>restarted, known version</em> invalidation for that object that has + * been acked by the client. + */ + private Map<ObjectIdP, Long> highestAckedVersionMap = new HashMap<ObjectIdP, Long>(); + + /** Records the fact that the client has acknowledged the given invalidation. */ + void recordAck(InvalidationP inv) { + if (!inv.getIsTrickleRestart() || !inv.getIsKnownVersion()) { + return; + } + + // If the invalidation version is newer than the highest acked version in the + // map, then update the map. + ObjectIdP objectId = inv.getObjectId(); + long version = inv.getVersion(); + if (version > getHighestAckedVersion(objectId)) { + highestAckedVersionMap.put(objectId, version); + } + } + + /** + * Returns true if the client has already acked a restarted invalidation with + * a version number greater than or equal to that in {@code inv} and the same + * object id, and {@code inv} is a known version invalidation. Unknown version + * invalidations are never considered already acked. + */ + boolean isAcked(InvalidationP inv) { + return inv.getIsKnownVersion() + && this.getHighestAckedVersion(inv.getObjectId()) >= inv.getVersion(); + } + + + /** + * Returns the highest acked version for the object id with the given key, or + * -1 if no versions have been acked. + */ + private long getHighestAckedVersion(ObjectIdP objectId) { + Long version = TypedUtil.mapGet(highestAckedVersionMap, objectId); + return (version != null) ? version : -1L; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/BasicSystemResources.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/BasicSystemResources.java new file mode 100644 index 0000000..d2c3abb --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/BasicSystemResources.java @@ -0,0 +1,125 @@ +/* + * 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; + +import com.google.ipc.invalidation.external.client.SystemResources; + + +/** + * A simple implementation of {@code SystemResources} that just takes the resource components + * and constructs a SystemResources object. + * + */ +public class BasicSystemResources implements SystemResources { + + // Components comprising the system resources. We delegate calls to these as appropriate. + private final Scheduler internalScheduler; + private final Scheduler listenerScheduler; + private final Logger logger; + private final NetworkChannel network; + private final Storage storage; + + /** The state of the resources. */ + private RunState runState = new RunState(); + + /** Information about the client operating system/platform, e.g., Windows, ChromeOS. */ + private final String platform; + + /** + * Constructs an instance from resource components. + * + * @param logger implementation of the logger + * @param internalScheduler scheduler for scheduling the library's internal events + * @param listenerScheduler scheduler for scheduling the listener's events + * @param network implementation of the network + * @param storage implementation of storage + * @param platform if not {@code null}, platform string for client version. If {@code null}, + * a default string will be constructed. + */ + public BasicSystemResources(Logger logger, Scheduler internalScheduler, + Scheduler listenerScheduler, NetworkChannel network, Storage storage, + String platform) { + this.logger = logger; + this.storage = storage; + this.network = network; + if (platform != null) { + this.platform = platform; + } else { + // If a platform string was not provided, try to compute a reasonable default. + this.platform = System.getProperty("os.name") + "/" + System.getProperty("os.version") + + "/" + System.getProperty("os.arch"); + } + + this.internalScheduler = internalScheduler; + this.listenerScheduler = listenerScheduler; + + // Pass a reference to this object to all of the components, so that they can access + // resources. E.g., so that the network can do logging. + logger.setSystemResources(this); + storage.setSystemResources(this); + network.setSystemResources(this); + internalScheduler.setSystemResources(this); + listenerScheduler.setSystemResources(this); + } + + @Override + public void start() { + runState.start(); + logger.info("Resources started"); + } + + @Override + public void stop() { + runState.stop(); + logger.info("Resources stopped"); + } + + @Override + public boolean isStarted() { + return runState.isStarted(); + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public Storage getStorage() { + return storage; + } + + @Override + public NetworkChannel getNetwork() { + return network; + } + + @Override + public Scheduler getInternalScheduler() { + return internalScheduler; + } + + @Override + public Scheduler getListenerScheduler() { + return listenerScheduler; + } + + @Override + public String getPlatform() { + return platform; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/CheckingInvalidationListener.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/CheckingInvalidationListener.java new file mode 100644 index 0000000..845037e --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/CheckingInvalidationListener.java @@ -0,0 +1,178 @@ +/* + * 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; + +import static com.google.ipc.invalidation.external.client.SystemResources.Scheduler.NO_DELAY; + +import com.google.ipc.invalidation.external.client.InvalidationClient; +import com.google.ipc.invalidation.external.client.InvalidationListener; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.external.client.types.Invalidation; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.Statistics.ListenerEventType; +import com.google.ipc.invalidation.util.NamedRunnable; +import com.google.ipc.invalidation.util.Preconditions; + + +/** + * {@link InvalidationListener} wrapper that ensures that a delegate listener is called on the + * proper thread and calls the listener method on the listener thread. + * + */ +class CheckingInvalidationListener implements InvalidationListener { + + /** The actual listener to which this listener delegates. */ + private final InvalidationListener delegate; + + /** The scheduler for scheduling internal events in the library. */ + private final Scheduler internalScheduler; + + /** The scheduler for scheduling events for the delegate. */ + private final Scheduler listenerScheduler; + + /** Statistics objects to track number of sent messages, etc. */ + private Statistics statistics; + + private final Logger logger; + + CheckingInvalidationListener(InvalidationListener delegate, Scheduler internalScheduler, + Scheduler listenerScheduler, Logger logger) { + this.delegate = Preconditions.checkNotNull(delegate, "Delegate cannot be null"); + this.internalScheduler = Preconditions.checkNotNull(internalScheduler, + "Internal scheduler cannot be null"); + this.listenerScheduler = Preconditions.checkNotNull(listenerScheduler, + "Listener scheduler cannot be null"); + this.logger = Preconditions.checkNotNull(logger, "Logger cannot be null"); + } + + void setStatistics(Statistics statistics) { + this.statistics = Preconditions.checkNotNull(statistics, "Statistics cannot be null"); + } + + @Override + public void invalidate(final InvalidationClient client, final Invalidation invalidation, + final AckHandle ackHandle) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + Preconditions.checkNotNull(ackHandle); + listenerScheduler.schedule(NO_DELAY, new NamedRunnable("CheckingInvalListener.invalidate") { + @Override + public void run() { + statistics.recordListenerEvent(ListenerEventType.INVALIDATE); + delegate.invalidate(client, invalidation, ackHandle); + } + }); + } + + @Override + public void invalidateUnknownVersion(final InvalidationClient client, final ObjectId objectId, + final AckHandle ackHandle) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + Preconditions.checkNotNull(ackHandle); + listenerScheduler.schedule(NO_DELAY, + new NamedRunnable("CheckingInvalListener.invalidateUnknownVersion") { + @Override + public void run() { + statistics.recordListenerEvent(ListenerEventType.INVALIDATE_UNKNOWN); + delegate.invalidateUnknownVersion(client, objectId, ackHandle); + } + }); + } + + @Override + public void invalidateAll(final InvalidationClient client, final AckHandle ackHandle) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + Preconditions.checkNotNull(ackHandle); + listenerScheduler.schedule(NO_DELAY, new NamedRunnable("CheckingInvalListener.invalidateAll") { + @Override + public void run() { + statistics.recordListenerEvent(ListenerEventType.INVALIDATE_ALL); + delegate.invalidateAll(client, ackHandle); + } + }); + } + + @Override + public void informRegistrationFailure(final InvalidationClient client, final ObjectId objectId, + final boolean isTransient, final String errorMessage) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + listenerScheduler.schedule(NO_DELAY, new NamedRunnable("CheckingInvalListener.regFailure") { + @Override + public void run() { + statistics.recordListenerEvent(ListenerEventType.INFORM_REGISTRATION_FAILURE); + delegate.informRegistrationFailure(client, objectId, isTransient, errorMessage); + } + }); + } + + @Override + public void informRegistrationStatus(final InvalidationClient client, final ObjectId objectId, + final RegistrationState regState) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + listenerScheduler.schedule(NO_DELAY, new NamedRunnable("CheckingInvalListener.regStatus") { + @Override + public void run() { + statistics.recordListenerEvent(ListenerEventType.INFORM_REGISTRATION_STATUS); + delegate.informRegistrationStatus(client, objectId, regState); + } + }); + } + + @Override + public void reissueRegistrations(final InvalidationClient client, final byte[] prefix, + final int prefixLen) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + listenerScheduler.schedule(NO_DELAY, new NamedRunnable("CheckingInvalListener.reissueRegs") { + @Override + public void run() { + statistics.recordListenerEvent(ListenerEventType.REISSUE_REGISTRATIONS); + delegate.reissueRegistrations(client, prefix, prefixLen); + } + }); + } + + @Override + public void informError(final InvalidationClient client, final ErrorInfo errorInfo) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + listenerScheduler.schedule(NO_DELAY, new NamedRunnable("CheckingInvalListener.informError") { + @Override + public void run() { + statistics.recordListenerEvent(ListenerEventType.INFORM_ERROR); + delegate.informError(client, errorInfo); + } + }); + } + + /** Returns the delegate {@link InvalidationListener}. */ + InvalidationListener getDelegate() { + return delegate; + } + + @Override + public void ready(final InvalidationClient client) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + listenerScheduler.schedule(NO_DELAY, new NamedRunnable("CheckingInvalListener.ready") { + @Override + public void run() { + logger.info("Informing app that ticl is ready"); + delegate.ready(client); + } + }); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/DigestStore.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/DigestStore.java new file mode 100644 index 0000000..05bde24 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/DigestStore.java @@ -0,0 +1,81 @@ +/* + * 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; + +import java.util.Collection; + +/** + * Interface for a store that allows objects to be added/removed along with + * the ability to get the digest for the whole or partial set of those objects. + * + * @param <ElementType> the type of the element stored in this + * + */ +public interface DigestStore<ElementType> { + + /** Returns the number of elements. */ + int size(); + + /** Returns whether {@code element} is in the store. */ + boolean contains(ElementType element); + + /** + * Returns a digest of the desired objects. + * <p> + * NOTE: the digest computations <b>MUST NOT</b> depend on the order in which the elements + * were added. + */ + byte[] getDigest(); + + /** + * Returns the elements whose digest prefixes begin with the bit prefix {@code digestPrefix}. + * {@code prefixLen} is the length of {@code digestPrefix} in bits, which may be less than + * {@code digestPrefix.length} (and may be 0). The implementing class can return *more* objects + * than what has been specified by {@code digestPrefix}, e.g., it could return all the objects + * in the store. + */ + Collection<ElementType> getElements(byte[] digestPrefix, int prefixLen); + + /** + * Adds {@code element} to the store. No-op if {@code element} is already present. + * @return whether the element was added + */ + boolean add(ElementType element); + + /** + * Adds {@code elements} to the store. If any element in {@code elements} is already present, + * the addition is a no-op for that element. + * @return the elements that were added + */ + Collection<ElementType> add(Collection<ElementType> elements); + + /** + * Removes {@code element} from the store. No-op if {@code element} is not present. + * @return whether the element was removed + */ + boolean remove(ElementType element); + + /** + * Remove {@code elements} to the store. If any element in {@code elements} is not present, the + * removal is a no-op for that element. + * @return the elements that were removed + */ + Collection<ElementType> remove(Collection<ElementType> elements); + + /** Removes all elements in this and returns them. */ + Collection<ElementType> removeAll(); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/InvalidationClientCore.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/InvalidationClientCore.java new file mode 100644 index 0000000..1eca3d4 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/InvalidationClientCore.java @@ -0,0 +1,1546 @@ +/* + * 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; + +import static com.google.ipc.invalidation.external.client.SystemResources.Scheduler.NO_DELAY; + +import com.google.ipc.invalidation.common.DigestFunction; +import com.google.ipc.invalidation.common.ObjectIdDigestUtils; +import com.google.ipc.invalidation.external.client.InvalidationListener; +import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState; +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.SystemResources.NetworkChannel; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.external.client.SystemResources.Storage; +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.Callback; +import com.google.ipc.invalidation.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.external.client.types.Invalidation; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.external.client.types.SimplePair; +import com.google.ipc.invalidation.external.client.types.Status; +import com.google.ipc.invalidation.ticl.ProtocolHandler.ParsedMessage; +import com.google.ipc.invalidation.ticl.ProtocolHandler.ProtocolListener; +import com.google.ipc.invalidation.ticl.ProtocolHandler.ServerMessageHeader; +import com.google.ipc.invalidation.ticl.Statistics.ClientErrorType; +import com.google.ipc.invalidation.ticl.Statistics.IncomingOperationType; +import com.google.ipc.invalidation.ticl.Statistics.ReceivedMessageType; +import com.google.ipc.invalidation.ticl.proto.ChannelCommon.NetworkEndpointId; +import com.google.ipc.invalidation.ticl.proto.Client.AckHandleP; +import com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState; +import com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState; +import com.google.ipc.invalidation.ticl.proto.Client.RunStateP; +import com.google.ipc.invalidation.ticl.proto.ClientConstants; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ErrorMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoRequestMessage.InfoType; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version; +import com.google.ipc.invalidation.ticl.proto.CommonProtos; +import com.google.ipc.invalidation.ticl.proto.JavaClient.InvalidationClientState; +import com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState; +import com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState; +import com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP; +import com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState; +import com.google.ipc.invalidation.util.Box; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.InternalBase; +import com.google.ipc.invalidation.util.Marshallable; +import com.google.ipc.invalidation.util.Preconditions; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.Smearer; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.ipc.invalidation.util.TypedUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.logging.Level; + + +/** + * Core implementation of the Invalidation Client Library (Ticl). Subclasses are required + * to implement concurrency control for the Ticl. + * + */ +public abstract class InvalidationClientCore extends InternalBase + implements Marshallable<InvalidationClientState>, ProtocolListener, + TestableInvalidationClient { + + /** + * A subclass of {@link RecurringTask} with simplified constructors to provide common + * parameters automatically (scheduler, logger, smearer). + */ + private abstract class TiclRecurringTask extends RecurringTask { + /** + * Constructs a task with {@code initialDelayMs} and {@code timeoutDelayMs}. If + * {@code useExponentialBackoff}, an exponential backoff generator with initial delay + * {@code timeoutDelayMs} is used as well; if not, exponential backoff is not used. + */ + TiclRecurringTask(String name, int initialDelayMs, int timeoutDelayMs, + boolean useExponentialBackoff) { + super(name, internalScheduler, logger, smearer, + useExponentialBackoff ? createExpBackOffGenerator(timeoutDelayMs, null) : null, + initialDelayMs, timeoutDelayMs); + } + + /** + * Constructs an instance from {@code marshalledState} that does not use exponential backoff. + * @param name name of the recurring task + */ + private TiclRecurringTask(String name, RecurringTaskState marshalledState) { + super(name, internalScheduler, logger, smearer, null, marshalledState); + } + + /** + * Constructs an instance from {@code marshalledState} that uses exponential backoff with an + * initial backoff of {@code timeoutMs}. + * + * @param name name of the recurring task + */ + private TiclRecurringTask(String name, int timeoutMs, RecurringTaskState marshalledState) { + super(name, internalScheduler, logger, smearer, + createExpBackOffGenerator(timeoutMs, marshalledState.getBackoffState()), marshalledState); + } + } + + /** A task for acquiring tokens from the server. */ + private class AcquireTokenTask extends TiclRecurringTask { + private static final String TASK_NAME = "AcquireToken"; + + AcquireTokenTask() { + super(TASK_NAME, NO_DELAY, config.getNetworkTimeoutDelayMs(), true); + } + + AcquireTokenTask(RecurringTaskState marshalledState) { + super(TASK_NAME, config.getNetworkTimeoutDelayMs(), marshalledState); + } + + @Override + public boolean runTask() { + // If token is still not assigned (as expected), sends a request. Otherwise, ignore. + if (clientToken == null) { + // Allocate a nonce and send a message requesting a new token. + setNonce(generateNonce(random)); + protocolHandler.sendInitializeMessage(applicationClientId, nonce, batchingTask, TASK_NAME); + return true; // Reschedule to check state, retry if necessary after timeout. + } else { + return false; // Don't reschedule. + } + } + } + + /** + * A task that schedules heartbeats when the registration summary at the client is not + * in sync with the registration summary from the server. + */ + private class RegSyncHeartbeatTask extends TiclRecurringTask { + private static final String TASK_NAME = "RegSyncHeartbeat"; + + RegSyncHeartbeatTask() { + super(TASK_NAME, config.getNetworkTimeoutDelayMs(), config.getNetworkTimeoutDelayMs(), true); + } + + RegSyncHeartbeatTask(RecurringTaskState marshalledState) { + super(TASK_NAME, config.getNetworkTimeoutDelayMs(), marshalledState); + } + + @Override + public boolean runTask() { + if (!registrationManager.isStateInSyncWithServer()) { + // Simply send an info message to ensure syncing happens. + logger.info("Registration state not in sync with server: %s", registrationManager); + sendInfoMessageToServer(false, true /* request server summary */); + return true; + } else { + logger.info("Not sending message since state is now in sync"); + return false; + } + } + } + + /** A task that writes the token to persistent storage. */ + private class PersistentWriteTask extends TiclRecurringTask { + /* + * This class implements a "train" of events that attempt to reliably write state to + * storage. The train continues until runTask encounters a termination condition, in + * which the state currently in memory and the state currently in storage match. + */ + + private static final String TASK_NAME = "PersistentWrite"; + + /** The last client token that was written to to persistent state successfully. */ + private final Box<PersistentTiclState> lastWrittenState = + Box.of(PersistentTiclState.DEFAULT_INSTANCE); + + PersistentWriteTask() { + super(TASK_NAME, NO_DELAY, config.getWriteRetryDelayMs(), true); + } + + PersistentWriteTask(RecurringTaskState marshalledState) { + super(TASK_NAME, config.getWriteRetryDelayMs(), marshalledState); + } + + @Override + public boolean runTask() { + if (clientToken == null) { + // We cannot write without a token. We must do this check before creating the + // PersistentTiclState because newPersistentTiclState cannot handle null tokens. + return false; + } + + // Compute the state that we will write if we decide to go ahead with the write. + final PersistentTiclState state = + PersistentTiclState.create(clientToken, lastMessageSendTimeMs); + byte[] serializedState = PersistenceUtils.serializeState(state, digestFn); + + // Decide whether or not to do the write. The decision varies depending on whether or + // not the channel supports offline delivery. If we decide not to do the write, then + // that means the in-memory and stored state match semantically, and the train stops. + if (config.getChannelSupportsOfflineDelivery()) { + // For offline delivery, we want the entire state to match, since we write the last + // send time for every message. + if (state.equals(lastWrittenState.get())) { + return false; + } + } else { + // If we do not support offline delivery, we avoid writing the state on each message, and + // we avoid checking the last-sent time (we check only the client token). + if (TypedUtil.<Bytes>equals( + state.getClientToken(), lastWrittenState.get().getClientToken())) { + return false; + } + } + + // We decided to do the write. + storage.writeKey(CLIENT_TOKEN_KEY, serializedState, new Callback<Status>() { + @Override + public void accept(Status status) { + logger.info("Write state completed: %s for %s", status, state); + Preconditions.checkState(resources.getInternalScheduler().isRunningOnThread()); + if (status.isSuccess()) { + // Set lastWrittenToken to be the token that was written (NOT clientToken - which + // could have changed while the write was happening). + lastWrittenState.set(state); + } else { + statistics.recordError(ClientErrorType.PERSISTENT_WRITE_FAILURE); + } + } + }); + return true; // Reschedule after timeout to make sure that write does happen. + } + } + + /** A task for sending heartbeats to the server. */ + private class HeartbeatTask extends TiclRecurringTask { + private static final String TASK_NAME = "Heartbeat"; + + /** Next time that the performance counters are sent to the server. */ + private long nextPerformanceSendTimeMs; + + HeartbeatTask() { + super(TASK_NAME, config.getHeartbeatIntervalMs(), NO_DELAY, false); + } + + HeartbeatTask(RecurringTaskState marshalledState) { + super(TASK_NAME, marshalledState); + } + + @Override + public boolean runTask() { + // Send info message. If needed, send performance counters and reset the next performance + // counter send time. + logger.info("Sending heartbeat to server: %s", this); + boolean mustSendPerfCounters = + nextPerformanceSendTimeMs > internalScheduler.getCurrentTimeMs(); + if (mustSendPerfCounters) { + this.nextPerformanceSendTimeMs = internalScheduler.getCurrentTimeMs() + + getSmearer().getSmearedDelay(config.getPerfCounterDelayMs()); + } + sendInfoMessageToServer(mustSendPerfCounters, !registrationManager.isStateInSyncWithServer()); + return true; // Reschedule. + } + } + + /** The task that is scheduled to send batched messages to the server (when needed). **/ + static class BatchingTask extends RecurringTask { + /* + * This class is static and extends RecurringTask directly so that it can be instantiated + * independently in ProtocolHandlerTest. + */ + private static final String TASK_NAME = "Batching"; + + /** {@link ProtocolHandler} instance from which messages will be pulled. */ + private final ProtocolHandler protocolHandler; + + /** Creates a new instance with default state. */ + BatchingTask(ProtocolHandler protocolHandler, SystemResources resources, Smearer smearer, + int batchingDelayMs) { + super(TASK_NAME, resources.getInternalScheduler(), resources.getLogger(), smearer, null, + batchingDelayMs, NO_DELAY); + this.protocolHandler = protocolHandler; + } + + /** Creates a new instance with state from {@code marshalledState}. */ + BatchingTask(ProtocolHandler protocolHandler, SystemResources resources, Smearer smearer, + RecurringTaskState marshalledState) { + super(TASK_NAME, resources.getInternalScheduler(), resources.getLogger(), smearer, null, + marshalledState); + this.protocolHandler = protocolHandler; + } + + @Override + public boolean runTask() { + protocolHandler.sendMessageToServer(); + return false; // Don't reschedule. + } + } + + /** + * A (slightly strange) recurring task that executes exactly once for the first heartbeat + * performed by a Ticl restarting from persistent state. The Android Ticl implementation + * requires that all work to be scheduled in the future occur in the form of a recurring task, + * hence this class. + */ + private class InitialPersistentHeartbeatTask extends TiclRecurringTask { + private static final String TASK_NAME = "InitialPersistentHeartbeat"; + + InitialPersistentHeartbeatTask(int delayMs) { + super(TASK_NAME, delayMs, NO_DELAY, false); + } + + @Override + public boolean runTask() { + sendInfoMessageToServer(false, true); + return false; // Don't reschedule. + } + } + + // + // End of nested classes. + // + + /** The single key used to write all the Ticl state. */ + public static final String CLIENT_TOKEN_KEY = "ClientToken"; + + /** Resources for the Ticl. */ + private final SystemResources resources; + + /** + * Reference into the resources object for cleaner code. All Ticl code must execute on this + * scheduler. + */ + private final Scheduler internalScheduler; + + /** Logger reference into the resources object for cleaner code. */ + private final Logger logger; + + /** Storage for the Ticl persistent state. */ + Storage storage; + + /** Application callback interface. */ + final InvalidationListener listener; + + /** Configuration for this instance. */ + private ClientConfigP config; + + /** Application identifier for this client. */ + private final ApplicationClientIdP applicationClientId; + + /** Object maintaining the registration state for this client. */ + private final RegistrationManager registrationManager; + + /** Object handling low-level wire format interactions. */ + private final ProtocolHandler protocolHandler; + + /** The function for computing the registration and persistence state digests. */ + private final DigestFunction digestFn = new ObjectIdDigestUtils.Sha1DigestFunction(); + + /** The state of the Ticl whether it has started or not. */ + private final RunState ticlState; + + /** Statistics objects to track number of sent messages, etc. */ + final Statistics statistics; + + /** A smearer to make sure that delays are randomized a little bit. */ + private final Smearer smearer; + + /** Current client token known from the server. */ + private Bytes clientToken = null; + + // After the client starts, exactly one of nonce and clientToken is non-null. + + /** If not {@code null}, nonce for pending identifier request. */ + private Bytes nonce = null; + + /** Whether we should send registrations to the server or not. */ + private boolean shouldSendRegistrations; + + /** Whether the network is online. Assume so when we start. */ + private boolean isOnline = true; + + /** A random number generator. */ + private final Random random; + + /** Last time a message was sent to the server. */ + private long lastMessageSendTimeMs = 0; + + /** A task for acquiring the token (if the client has no token). */ + private AcquireTokenTask acquireTokenTask; + + /** Task for checking if reg summary is out of sync and then sending a heartbeat to the server. */ + private RegSyncHeartbeatTask regSyncHeartbeatTask; + + /** Task for writing the state blob to persistent storage. */ + private PersistentWriteTask persistentWriteTask; + + /** A task for periodic heartbeats. */ + private HeartbeatTask heartbeatTask; + + /** Task to send all batched messages to the server. */ + private BatchingTask batchingTask; + + /** Task to do the first heartbeat after a persistent restart. */ + private InitialPersistentHeartbeatTask initialPersistentHeartbeatTask; + + /** A cache of already acked invalidations to avoid duplicate delivery. */ + private final AckCache ackCache = new AckCache(); + + /** + * Constructs a client. + * + * @param resources resources to use during execution + * @param random a random number generator + * @param clientType client type code + * @param clientName application identifier for the client + * @param config configuration for the client + * @param applicationName name of the application using the library (for debugging/monitoring) + * @param regManagerState marshalled registration manager state, if any + * @param protocolHandlerState marshalled protocol handler state, if any + * @param listener application callback + */ + private InvalidationClientCore(final SystemResources resources, Random random, int clientType, + final byte[] clientName, ClientConfigP config, String applicationName, + RunStateP ticlRunState, + RegistrationManagerStateP regManagerState, + ProtocolHandlerState protocolHandlerState, + StatisticsState statisticsState, + InvalidationListener listener) { + this.resources = Preconditions.checkNotNull(resources); + this.random = random; + this.logger = Preconditions.checkNotNull(resources.getLogger()); + this.internalScheduler = resources.getInternalScheduler(); + this.storage = resources.getStorage(); + this.config = config; + this.ticlState = (ticlRunState == null) ? new RunState() : new RunState(ticlRunState); + this.smearer = new Smearer(random, this.config.getSmearPercent()); + this.applicationClientId = ApplicationClientIdP.create(clientType, new Bytes(clientName)); + this.listener = listener; + this.statistics = (statisticsState != null) + ? Statistics.deserializeStatistics(resources.getLogger(), statisticsState.getCounter()) + : new Statistics(); + this.registrationManager = new RegistrationManager(logger, statistics, digestFn, + regManagerState); + this.protocolHandler = new ProtocolHandler(config.getProtocolHandlerConfig(), resources, + smearer, statistics, clientType, applicationName, this, protocolHandlerState); + } + + /** + * Constructs a client with default state. + * + * @param resources resources to use during execution + * @param random a random number generator + * @param clientType client type code + * @param clientName application identifier for the client + * @param config configuration for the client + * @param applicationName name of the application using the library (for debugging/monitoring) + * @param listener application callback + */ + public InvalidationClientCore(final SystemResources resources, Random random, int clientType, + final byte[] clientName, ClientConfigP config, String applicationName, + InvalidationListener listener) { + this(resources, random, clientType, clientName, config, applicationName, null, null, null, null, + listener); + createSchedulingTasks(null); + registerWithNetwork(resources); + logger.info("Created client: %s", this); + } + + /** + * Constructs a client with state initialized from {@code marshalledState}. + * + * @param resources resources to use during execution + * @param random a random number generator + * @param clientType client type code + * @param clientName application identifier for the client + * @param config configuration for the client + * @param applicationName name of the application using the library (for debugging/monitoring) + * @param listener application callback + */ + public InvalidationClientCore(final SystemResources resources, Random random, int clientType, + final byte[] clientName, ClientConfigP config, String applicationName, + InvalidationClientState marshalledState, InvalidationListener listener) { + this(resources, random, clientType, clientName, config, applicationName, + marshalledState.getRunState(), marshalledState.getRegistrationManagerState(), + marshalledState.getProtocolHandlerState(), marshalledState.getStatisticsState(), listener); + // Unmarshall. + if (marshalledState.hasClientToken()) { + clientToken = marshalledState.getClientToken(); + } + if (marshalledState.hasNonce()) { + nonce = marshalledState.getNonce(); + } + this.shouldSendRegistrations = marshalledState.getShouldSendRegistrations(); + this.lastMessageSendTimeMs = marshalledState.getLastMessageSendTimeMs(); + this.isOnline = marshalledState.getIsOnline(); + createSchedulingTasks(marshalledState); + + // We register with the network after unmarshalling our isOnline value. This is because when + // we register with the network, it may give us a new value for isOnline. If we unmarshalled + // after registering, then we would clobber the new value with the old marshalled value, which + // is wrong. + registerWithNetwork(resources); + logger.info("Created client: %s", this); + } + + /** + * Registers handlers for received messages and network status changes with the network of + * {@code resources}. + */ + private void registerWithNetwork(final SystemResources resources) { + resources.getNetwork().setListener(new NetworkChannel.NetworkListener() { + @Override + public void onMessageReceived(byte[] incomingMessage) { + InvalidationClientCore.this.handleIncomingMessage(incomingMessage); + } + @Override + public void onOnlineStatusChange(boolean isOnline) { + InvalidationClientCore.this.handleNetworkStatusChange(isOnline); + } + @Override + public void onAddressChange() { + // Send a message to the server. The header will include the new network address. + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + sendInfoMessageToServer(false, false); + } + }); + } + + /** Returns a default config builder for the client. */ + public static ClientConfigP createConfig() { + Version version = + Version.create(ClientConstants.CONFIG_MAJOR_VERSION, ClientConstants.CONFIG_MINOR_VERSION); + ProtocolHandlerConfigP protocolHandlerConfig = ProtocolHandler.createConfig(); + ClientConfigP.Builder builder = new ClientConfigP.Builder(version, protocolHandlerConfig); + return builder.build(); + } + + /** Returns a configuration builder with parameters set for unit tests. */ + public static ClientConfigP createConfigForTest() { + Version version = + Version.create(ClientConstants.CONFIG_MAJOR_VERSION, ClientConstants.CONFIG_MINOR_VERSION); + ProtocolHandlerConfigP protocolHandlerConfig = ProtocolHandler.createConfigForTest(); + ClientConfigP.Builder builder = new ClientConfigP.Builder(version, protocolHandlerConfig); + builder.networkTimeoutDelayMs = 2 * 1000; + builder.heartbeatIntervalMs = 5 * 1000; + builder.writeRetryDelayMs = 500; + return builder.build(); + } + + /** + * Creates the tasks used by the Ticl for token acquisition, heartbeats, persistent writes and + * registration sync. + * + * @param marshalledState saved state of recurring tasks + */ + private void createSchedulingTasks(InvalidationClientState marshalledState) { + if (marshalledState == null) { + this.acquireTokenTask = new AcquireTokenTask(); + this.heartbeatTask = new HeartbeatTask(); + this.regSyncHeartbeatTask = new RegSyncHeartbeatTask(); + this.persistentWriteTask = new PersistentWriteTask(); + this.batchingTask = new BatchingTask(protocolHandler, resources, smearer, + config.getProtocolHandlerConfig().getBatchingDelayMs()); + } else { + this.acquireTokenTask = new AcquireTokenTask(marshalledState.getAcquireTokenTaskState()); + this.heartbeatTask = new HeartbeatTask(marshalledState.getHeartbeatTaskState()); + this.regSyncHeartbeatTask = + new RegSyncHeartbeatTask(marshalledState.getRegSyncHeartbeatTaskState()); + this.persistentWriteTask = + new PersistentWriteTask(marshalledState.getPersistentWriteTaskState()); + this.batchingTask = new BatchingTask(protocolHandler, resources, smearer, + marshalledState.getBatchingTaskState()); + if (marshalledState.hasLastWrittenState()) { + persistentWriteTask.lastWrittenState.set(marshalledState.getLastWrittenState()); + } + } + // The handling of new InitialPersistentHeartbeatTask is a little strange. We create one when + // the Ticl is first created so that it can be called by the scheduler if it had been scheduled + // in the past. Otherwise, when we are ready to schedule one ourselves, we create a new instance + // with the proper delay, then schedule it. We have to do this because we don't know what delay + // to use here, since we don't compute it until start(). + this.initialPersistentHeartbeatTask = new InitialPersistentHeartbeatTask(0); + } + + /** Returns the configuration used by the client. */ + protected ClientConfigP getConfig() { + return config; + } + + // Methods for TestableInvalidationClient. + + @Override + + public ClientConfigP getConfigForTest() { + return getConfig(); + } + + @Override + + public byte[] getApplicationClientIdForTest() { + return applicationClientId.toByteArray(); + } + + /** Returns the application client id of this client. */ + protected ApplicationClientIdP getApplicationClientIdP() { + return applicationClientId; + } + + @Override + + public InvalidationListener getInvalidationListenerForTest() { + return (listener instanceof CheckingInvalidationListener) ? + ((CheckingInvalidationListener) this.listener).getDelegate() : this.listener; + } + + + public SystemResources getResourcesForTest() { + return resources; + } + + public SystemResources getResources() { + return resources; + } + + @Override + + public Statistics getStatisticsForTest() { + return statistics; + } + + Statistics getStatistics() { + return statistics; + } + + @Override + + public DigestFunction getDigestFunctionForTest() { + return this.digestFn; + } + + @Override + + public long getNextMessageSendTimeMsForTest() { + Preconditions.checkState(resources.getInternalScheduler().isRunningOnThread()); + return protocolHandler.getNextMessageSendTimeMsForTest(); + } + + @Override + + public RegistrationManagerState getRegistrationManagerStateCopyForTest() { + Preconditions.checkState(resources.getInternalScheduler().isRunningOnThread()); + return registrationManager.getRegistrationManagerStateCopyForTest(); + } + + @Override + + public void changeNetworkTimeoutDelayForTest(int networkTimeoutDelayMs) { + ClientConfigP.Builder builder = config.toBuilder(); + builder.networkTimeoutDelayMs = networkTimeoutDelayMs; + config = builder.build(); + createSchedulingTasks(null); + } + + @Override + + public void changeHeartbeatDelayForTest(int heartbeatDelayMs) { + ClientConfigP.Builder builder = config.toBuilder(); + builder.heartbeatIntervalMs = heartbeatDelayMs; + config = builder.build(); + createSchedulingTasks(null); + } + + @Override + + public void setDigestStoreForTest(DigestStore<ObjectIdP> digestStore) { + Preconditions.checkState(!resources.isStarted()); + registrationManager.setDigestStoreForTest(digestStore); + } + + @Override + + public Bytes getClientTokenForTest() { + return getClientToken(); + } + + @Override + + public String getClientTokenKeyForTest() { + return CLIENT_TOKEN_KEY; + } + + @Override + public boolean isStartedForTest() { + return isStarted(); + } + + /** + * Returns whether the Ticl is started, i.e., whether it at some point had a session with the + * data center after being constructed. + */ + protected boolean isStarted() { + return ticlState.isStarted(); + } + + @Override + public void stopResources() { + resources.stop(); + } + + @Override + public long getResourcesTimeMs() { + return resources.getInternalScheduler().getCurrentTimeMs(); + } + + @Override + public Scheduler getInternalSchedulerForTest() { + return resources.getInternalScheduler(); + } + + @Override + public Storage getStorage() { + return storage; + } + + @Override + public NetworkEndpointId getNetworkIdForTest() { + NetworkChannel network = resources.getNetwork(); + if (!(network instanceof TestableNetworkChannel)) { + throw new UnsupportedOperationException( + "getNetworkIdForTest requires a TestableNetworkChannel, not: " + network.getClass()); + } + return ((TestableNetworkChannel) network).getNetworkIdForTest(); + } + + // End of methods for TestableInvalidationClient + + @Override // InvalidationClient + public void start() { + Preconditions.checkState(resources.isStarted(), "Resources must be started before starting " + + "the Ticl"); + if (ticlState.isStarted()) { + logger.severe("Ignoring start call since already started: client = %s", this); + return; + } + + // Initialize the nonce so that we can maintain the invariant that exactly one of + // "nonce" and "clientToken" is non-null. + setNonce(generateNonce(random)); + + logger.info("Starting with Java config: %s", config); + // Read the state blob and then schedule startInternal once the value is there. + scheduleStartAfterReadingStateBlob(); + } + + /** + * Implementation of {@link #start} on the internal thread with the persistent + * {@code serializedState} if any. Starts the TICL protocol and makes the TICL ready to receive + * registrations, invalidations, etc. + */ + private void startInternal(byte[] serializedState) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + + // Initialize the session manager using the persisted client token. + PersistentTiclState persistentState = + (serializedState == null) ? null : PersistenceUtils.deserializeState(logger, + serializedState, digestFn); + + if ((serializedState != null) && (persistentState == null)) { + // In this case, we'll proceed as if we had no persistent state -- i.e., obtain a new client + // id from the server. + statistics.recordError(ClientErrorType.PERSISTENT_DESERIALIZATION_FAILURE); + logger.severe("Failed deserializing persistent state: %s", + Bytes.toLazyCompactString(serializedState)); + } + if (persistentState != null) { + // If we have persistent state, use the previously-stored token and send a heartbeat to + // let the server know that we've restarted, since we may have been marked offline. + + // In the common case, the server will already have all of our + // registrations, but we won't know for sure until we've gotten its summary. + // We'll ask the application for all of its registrations, but to avoid + // making the registrar redo the work of performing registrations that + // probably already exist, we'll suppress sending them to the registrar. + logger.info("Restarting from persistent state: %s", persistentState.getClientToken()); + setNonce(null); + setClientToken(persistentState.getClientToken()); + shouldSendRegistrations = false; + + // Schedule an info message for the near future. + int initialHeartbeatDelayMs = computeInitialPersistentHeartbeatDelayMs( + config, resources, persistentState.getLastMessageSendTimeMs()); + initialPersistentHeartbeatTask = new InitialPersistentHeartbeatTask(initialHeartbeatDelayMs); + initialPersistentHeartbeatTask.ensureScheduled(""); + + // We need to ensure that heartbeats are sent, regardless of whether we start fresh or from + // persistent state. The line below ensures that they are scheduled in the persistent startup + // case. For the other case, the task is scheduled when we acquire a token. + heartbeatTask.ensureScheduled("Startup-after-persistence"); + } else { + // If we had no persistent state or couldn't deserialize the state that we had, start fresh. + // Request a new client identifier. + + // The server can't possibly have our registrations, so whatever we get + // from the application we should send to the registrar. + logger.info("Starting with no previous state"); + shouldSendRegistrations = true; + acquireToken("Startup"); + } + + // listener.ready() is called when ticl has acquired a new token. + } + + /** + * Returns the delay for the initial heartbeat, given that the last message to the server was + * sent at {@code lastSendTimeMs}. + * @param config configuration object used by the client + * @param resources resources used by the client + */ + + static int computeInitialPersistentHeartbeatDelayMs(ClientConfigP config, + SystemResources resources, long lastSendTimeMs) { + // There are five cases: + // 1. Channel does not support offline delivery. We delay a little bit to allow the + // application to reissue its registrations locally and avoid triggering registration + // sync with the data center due to a hash mismatch. This is the "minimum delay," and we + // never use a delay less than it. + // + // All other cases are for channels supporting offline delivery. + // + // 2. Last send time is in the future (something weird happened). Use the minimum delay. + // 3. We have been asleep for more than one heartbeat interval. Use the minimum delay. + // 4. We have been asleep for less than one heartbeat interval. + // (a). The time remaining to the end of the interval is less than the minimum delay. + // Use the minimum delay. + // (b). The time remaining to the end of the interval is more than the minimum delay. + // Use the remaining delay. + final long nowMs = resources.getInternalScheduler().getCurrentTimeMs(); + final int initialHeartbeatDelayMs; + if (!config.getChannelSupportsOfflineDelivery()) { + // Case 1. + initialHeartbeatDelayMs = config.getInitialPersistentHeartbeatDelayMs(); + } else { + // Offline delivery cases (2, 3, 4). + // The default of the last send time is zero, so even if it wasn't written in the persistent + // state, this logic is still correct. + if ((lastSendTimeMs > nowMs) || // Case 2. + ((lastSendTimeMs + config.getHeartbeatIntervalMs()) < nowMs)) { // Case 3. + // Either something strange happened and the last send time is in the future, or we + // have been asleep for more than one heartbeat interval. Send immediately. + initialHeartbeatDelayMs = config.getInitialPersistentHeartbeatDelayMs(); + } else { + // Case 4. + // We have been asleep for less than one heartbeat interval. Send after it expires, + // but ensure we let the initial heartbeat interval elapse. + final long timeSinceLastMessageMs = nowMs - lastSendTimeMs; + final int remainingHeartbeatIntervalMs = + (int) (config.getHeartbeatIntervalMs() - timeSinceLastMessageMs); + initialHeartbeatDelayMs = Math.max(remainingHeartbeatIntervalMs, + config.getInitialPersistentHeartbeatDelayMs()); + } + } + resources.getLogger().info("Computed heartbeat delay %s from: offline-delivery = %s, " + + "initial-persistent-delay = %s, heartbeat-interval = %s, nowMs = %s", + initialHeartbeatDelayMs, config.getChannelSupportsOfflineDelivery(), + config.getInitialPersistentHeartbeatDelayMs(), config.getHeartbeatIntervalMs(), + nowMs); + return initialHeartbeatDelayMs; + } + + @Override // InvalidationClient + public void stop() { + logger.warning("Ticl being stopped: %s", InvalidationClientCore.this); + if (ticlState.isStarted()) { // RunState is thread-safe. + ticlState.stop(); + } + } + + @Override // InvalidationClient + public void register(ObjectId objectId) { + List<ObjectId> objectIds = new ArrayList<ObjectId>(); + objectIds.add(objectId); + performRegisterOperations(objectIds, RegistrationP.OpType.REGISTER); + } + + @Override // InvalidationClient + public void unregister(ObjectId objectId) { + List<ObjectId> objectIds = new ArrayList<ObjectId>(); + objectIds.add(objectId); + performRegisterOperations(objectIds, RegistrationP.OpType.UNREGISTER); + } + + @Override // InvalidationClient + public void register(Collection<ObjectId> objectIds) { + performRegisterOperations(objectIds, RegistrationP.OpType.REGISTER); + } + + @Override // InvalidationClient + public void unregister(Collection<ObjectId> objectIds) { + performRegisterOperations(objectIds, RegistrationP.OpType.UNREGISTER); + } + + /** + * Implementation of (un)registration. + * + * @param objectIds object ids on which to operate + * @param regOpType whether to register or unregister + */ + private void performRegisterOperations(final Collection<ObjectId> objectIds, + final int regOpType) { + Preconditions.checkState(!objectIds.isEmpty(), "Must specify some object id"); + Preconditions.checkState(internalScheduler.isRunningOnThread(), + "Not running on internal thread"); + + if (ticlState.isStopped()) { + // The Ticl has been stopped. This might be some old registration op coming in. Just ignore + // instead of crashing. + logger.severe("Ticl stopped: register (%s) of %s ignored.", regOpType, objectIds); + return; + } + if (!ticlState.isStarted()) { + // We must be in the NOT_STARTED state, since we can't be in STOPPED or STARTED (since the + // previous if-check didn't succeeded, and isStarted uses a != STARTED test). + logger.severe( + "Ticl is not yet started; failing registration call; client = %s, objects = %s, op = %s", + this, objectIds, regOpType); + for (ObjectId objectId : objectIds) { + listener.informRegistrationFailure(this, objectId, true, "Client not yet ready"); + } + return; + } + + List<ObjectIdP> objectIdProtos = new ArrayList<ObjectIdP>(objectIds.size()); + for (ObjectId objectId : objectIds) { + Preconditions.checkNotNull(objectId, "Must specify object id"); + ObjectIdP objectIdProto = ProtoWrapperConverter.convertToObjectIdProto(objectId); + IncomingOperationType opType = (regOpType == RegistrationP.OpType.REGISTER) ? + IncomingOperationType.REGISTRATION : IncomingOperationType.UNREGISTRATION; + statistics.recordIncomingOperation(opType); + logger.info("Register %s, %s", objectIdProto, regOpType); + objectIdProtos.add(objectIdProto); + } + + // Update the registration manager state, then have the protocol client send a message. + // performOperations returns only those elements of objectIdProtos that caused a state + // change (i.e., elements not present if regOpType == REGISTER or elements that were present + // if regOpType == UNREGISTER). + Collection<ObjectIdP> objectProtosToSend = registrationManager.performOperations( + objectIdProtos, regOpType); + + // Check whether we should suppress sending registrations because we don't + // yet know the server's summary. + if (shouldSendRegistrations && (!objectProtosToSend.isEmpty())) { + protocolHandler.sendRegistrations(objectProtosToSend, regOpType, batchingTask); + } + InvalidationClientCore.this.regSyncHeartbeatTask.ensureScheduled("performRegister"); + } + + @Override // InvalidationClient + public void acknowledge(final AckHandle acknowledgeHandle) { + Preconditions.checkNotNull(acknowledgeHandle); + Preconditions.checkState(internalScheduler.isRunningOnThread(), + "Not running on internal thread"); + + // Parse and validate the ack handle first. + AckHandleP ackHandle; + try { + ackHandle = AckHandleP.parseFrom(acknowledgeHandle.getHandleData()); + } catch (ValidationException exception) { + logger.warning("Bad ack handle : %s", + Bytes.toLazyCompactString(acknowledgeHandle.getHandleData())); + statistics.recordError(ClientErrorType.ACKNOWLEDGE_HANDLE_FAILURE); + return; + } + + // Currently, only invalidations have non-trivial ack handle. + InvalidationP invalidation = ackHandle.getNullableInvalidation(); + if (invalidation == null) { + logger.warning("Ack handle without invalidation : %s", + Bytes.toLazyCompactString(acknowledgeHandle.getHandleData())); + statistics.recordError(ClientErrorType.ACKNOWLEDGE_HANDLE_FAILURE); + return; + } + + // Don't send the payload back. + if (invalidation.hasPayload()) { + InvalidationP.Builder builder = invalidation.toBuilder(); + builder.payload = null; + invalidation = builder.build(); + } + statistics.recordIncomingOperation(IncomingOperationType.ACKNOWLEDGE); + protocolHandler.sendInvalidationAck(invalidation, batchingTask); + + // Record that the invalidation has been acknowledged to potentially avoid unnecessary delivery + // of earlier invalidations for the same object. + ackCache.recordAck(invalidation); + } + + // + // Protocol listener methods + // + + @Override + public Bytes getClientToken() { + Preconditions.checkState((clientToken == null) || (nonce == null)); + return clientToken; + } + + @Override + public void handleMessageSent() { + // The ProtocolHandler just sent a message to the server. If the channel supports offline + // delivery (see the comment in the ClientConfigP), store this time to stable storage. This + // only needs to be a best-effort write; if it fails, then we will "forget" that we sent the + // message and heartbeat needlessly when next restarted. That is a performance/battery bug, + // not a correctness bug. + lastMessageSendTimeMs = getResourcesTimeMs(); + if (config.getChannelSupportsOfflineDelivery()) { + // Write whether or not we have a token. The persistent write task is a no-op if there is + // no token. We only write if the channel supports offline delivery. We could do the write + // regardless, and may want to do so in the future, since it might simplify some of the + // Ticl implementation. + persistentWriteTask.ensureScheduled("sent-message"); + } + } + + @Override + public RegistrationSummary getRegistrationSummary() { + return registrationManager.getRegistrationSummary(); + } + + // + // Private methods and toString. + // + + void handleNetworkStatusChange(final boolean isOnline) { + // If we're back online and haven't sent a message to the server in a while, send a heartbeat to + // make sure the server knows we're online. + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + boolean wasOnline = this.isOnline; + this.isOnline = isOnline; + if (isOnline && !wasOnline && (internalScheduler.getCurrentTimeMs() > + lastMessageSendTimeMs + config.getOfflineHeartbeatThresholdMs())) { + logger.log(Level.INFO, + "Sending heartbeat after reconnection, previous send was %s ms ago", + internalScheduler.getCurrentTimeMs() - lastMessageSendTimeMs); + sendInfoMessageToServer(false, !registrationManager.isStateInSyncWithServer()); + } + } + + /** + * Handles an {@code incomingMessage} from the data center. If it is valid and addressed to + * this client, dispatches to methods to handle sub-parts of the message; if not, drops the + * message. + */ + void handleIncomingMessage(byte[] incomingMessage) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + statistics.recordReceivedMessage(ReceivedMessageType.TOTAL); + ParsedMessage parsedMessage = protocolHandler.handleIncomingMessage(incomingMessage); + if (parsedMessage == null) { + // Invalid message. + return; + } + + // Ensure we have either a matching token or a matching nonce. + if (!validateToken(parsedMessage)) { + return; + } + + // Handle a token-control message, if present. + if (parsedMessage.tokenControlMessage != null) { + statistics.recordReceivedMessage(ReceivedMessageType.TOKEN_CONTROL); + handleTokenChanged(parsedMessage.header.token, + parsedMessage.tokenControlMessage.hasNewToken() ? + parsedMessage.tokenControlMessage.getNewToken() : null); + } + + // We might have lost our token or failed to acquire one. Ensure that we do not proceed in + // either case. + if (clientToken == null) { + return; + } + + // First, handle the message header. + handleIncomingHeader(parsedMessage.header); + + // Then, handle any work remaining in the message. + if (parsedMessage.invalidationMessage != null) { + statistics.recordReceivedMessage(ReceivedMessageType.INVALIDATION); + handleInvalidations(parsedMessage.invalidationMessage.getInvalidation()); + } + if (parsedMessage.registrationStatusMessage != null) { + statistics.recordReceivedMessage(ReceivedMessageType.REGISTRATION_STATUS); + handleRegistrationStatus(parsedMessage.registrationStatusMessage.getRegistrationStatus()); + } + if (parsedMessage.registrationSyncRequestMessage != null) { + statistics.recordReceivedMessage(ReceivedMessageType.REGISTRATION_SYNC_REQUEST); + handleRegistrationSyncRequest(); + } + if (parsedMessage.infoRequestMessage != null) { + statistics.recordReceivedMessage(ReceivedMessageType.INFO_REQUEST); + handleInfoMessage(parsedMessage.infoRequestMessage.getInfoType()); + } + if (parsedMessage.errorMessage != null) { + statistics.recordReceivedMessage(ReceivedMessageType.ERROR); + handleErrorMessage(parsedMessage.header, parsedMessage.errorMessage.getCode(), + parsedMessage.errorMessage.getDescription()); + } + } + + /** + * Handles a token-control message. + * @param headerToken token in the server message + * @param newToken the new token provided, or {@code null} if this is a destroy message. + */ + private void handleTokenChanged(Bytes headerToken, final Bytes newToken) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + + // The server is either supplying a new token in response to an InitializeMessage, spontaneously + // destroying a token we hold, or spontaneously upgrading a token we hold. + + if (newToken != null) { + // Note: headerToken cannot be null, so a null nonce or clientToken will always be non-equal. + boolean headerTokenMatchesNonce = TypedUtil.<Bytes>equals(headerToken, nonce); + boolean headerTokenMatchesExistingToken = TypedUtil.<Bytes>equals(headerToken, clientToken); + boolean shouldAcceptToken = headerTokenMatchesNonce || headerTokenMatchesExistingToken; + if (!shouldAcceptToken) { + logger.info("Ignoring new token; %s does not match nonce = %s or existing token = %s", + newToken, nonce, clientToken); + return; + } + logger.info("New token being assigned at client: %s, Old = %s", newToken, clientToken); + + // Start the regular heartbeats now. + heartbeatTask.ensureScheduled("Heartbeat-after-new-token"); + setNonce(null); + setClientToken(newToken); + persistentWriteTask.ensureScheduled("Write-after-new-token"); + } else { + logger.info("Destroying existing token: %s", clientToken); + acquireToken("Destroy"); + } + } + + /** Handles a server {@code header}. */ + private void handleIncomingHeader(ServerMessageHeader header) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + if (nonce != null) { + throw new IllegalStateException( + "Cannot process server header with non-null nonce (have " + nonce + "): " + header); + } + if (header.registrationSummary != null) { + // We've received a summary from the server, so if we were suppressing registrations, we + // should now allow them to go to the registrar. + shouldSendRegistrations = true; + + // Pass the registration summary to the registration manager. If we are now in agreement + // with the server and we had any pending operations, we can tell the listener that those + // operations have succeeded. + Set<RegistrationP> upcalls = + registrationManager.informServerRegistrationSummary(header.registrationSummary); + logger.fine("Received new server registration summary (%s); will make %s upcalls", + header.registrationSummary, upcalls.size()); + for (RegistrationP registration : upcalls) { + ObjectId objectId = + ProtoWrapperConverter.convertFromObjectIdProto(registration.getObjectId()); + RegistrationState regState = convertOpTypeToRegState(registration.getOpType()); + listener.informRegistrationStatus(this, objectId, regState); + } + } + } + + /** Handles incoming {@code invalidations}. */ + private void handleInvalidations(Collection<InvalidationP> invalidations) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + + for (InvalidationP invalidation : invalidations) { + AckHandle ackHandle = AckHandle.newInstance(AckHandleP.create(invalidation).toByteArray()); + if (ackCache.isAcked(invalidation)) { + // If the ack cache indicates that the client has already acked a restarted invalidation + // with an equal or greater version, then the TICL can simply acknowledge it immediately + // rather than delivering it to the listener. + logger.info("Stale invalidation {0}, not delivering", invalidation); + acknowledge(ackHandle); + statistics.recordReceivedMessage(ReceivedMessageType.STALE_INVALIDATION); + } else if (CommonProtos.isAllObjectId(invalidation.getObjectId())) { + logger.info("Issuing invalidate all"); + listener.invalidateAll(InvalidationClientCore.this, ackHandle); + } else { + // Regular object. Could be unknown version or not. + Invalidation inv = ProtoWrapperConverter.convertFromInvalidationProto(invalidation); + + boolean isSuppressed = invalidation.getIsTrickleRestart(); + logger.info("Issuing invalidate (known-version = %s, is-trickle-restart = %s): %s", + invalidation.getIsKnownVersion(), isSuppressed, inv); + + // Issue invalidate if the invalidation had a known version AND either no suppression has + // occurred or the client allows suppression. + if (invalidation.getIsKnownVersion() && + (!isSuppressed || InvalidationClientCore.this.config.getAllowSuppression())) { + listener.invalidate(InvalidationClientCore.this, inv, ackHandle); + } else { + // Otherwise issue invalidateUnknownVersion. + listener.invalidateUnknownVersion(InvalidationClientCore.this, inv.getObjectId(), + ackHandle); + } + } + } + } + + /** Handles incoming registration statuses. */ + private void handleRegistrationStatus(List<RegistrationStatus> regStatusList) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + List<Boolean> localProcessingStatuses = + registrationManager.handleRegistrationStatus(regStatusList); + Preconditions.checkState(localProcessingStatuses.size() == regStatusList.size(), + "Not all registration statuses were processed"); + + // Inform app about the success or failure of each registration based + // on what the registration manager has indicated. + for (int i = 0; i < regStatusList.size(); ++i) { + RegistrationStatus regStatus = regStatusList.get(i); + boolean wasSuccess = localProcessingStatuses.get(i); + logger.fine("Process reg status: %s", regStatus); + + ObjectId objectId = ProtoWrapperConverter.convertFromObjectIdProto( + regStatus.getRegistration().getObjectId()); + if (wasSuccess) { + // Server operation was both successful and agreed with what the client wanted. + int regOpType = regStatus.getRegistration().getOpType(); + InvalidationListener.RegistrationState regState = convertOpTypeToRegState(regOpType); + listener.informRegistrationStatus(InvalidationClientCore.this, objectId, regState); + } else { + // Server operation either failed or disagreed with client's intent (e.g., successful + // unregister, but the client wanted a registration). + String description = CommonProtos.isSuccess(regStatus.getStatus()) + ? "Registration discrepancy detected" : regStatus.getStatus().getDescription(); + boolean isPermanent = CommonProtos.isPermanentFailure(regStatus.getStatus()); + listener.informRegistrationFailure(InvalidationClientCore.this, objectId, !isPermanent, + description); + } + } + } + + /** Handles a registration sync request. */ + private void handleRegistrationSyncRequest() { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + // Send all the registrations in the reg sync message. + // Generate a single subtree for all the registrations. + RegistrationSubtree subtree = + registrationManager.getRegistrations(Bytes.EMPTY_BYTES.getByteArray(), 0); + protocolHandler.sendRegistrationSyncSubtree(subtree, batchingTask); + } + + /** Handles an info message request. */ + private void handleInfoMessage(Collection<Integer> infoTypes) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + boolean mustSendPerformanceCounters = false; + for (int infoType : infoTypes) { + mustSendPerformanceCounters = (infoType == InfoType.GET_PERFORMANCE_COUNTERS); + if (mustSendPerformanceCounters) { + break; + } + } + sendInfoMessageToServer(mustSendPerformanceCounters, + !registrationManager.isStateInSyncWithServer()); + } + + /** Handles an error message. */ + private void handleErrorMessage(ServerMessageHeader header, int code, String description) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + + // If it is an auth failure, we shut down the ticl. + logger.severe("Received error message: %s, %s, %s", header, code, description); + + // Translate the code to error reason. + int reason; + switch (code) { + case ErrorMessage.Code.AUTH_FAILURE: + reason = ErrorInfo.ErrorReason.AUTH_FAILURE; + break; + case ErrorMessage.Code.UNKNOWN_FAILURE: + default: + reason = ErrorInfo.ErrorReason.UNKNOWN_FAILURE; + break; + } + + // Issue an informError to the application. + ErrorInfo errorInfo = ErrorInfo.newInstance(reason, false, description, null); + listener.informError(this, errorInfo); + + // If this is an auth failure, remove registrations and stop the Ticl. Otherwise do nothing. + if (code != ErrorMessage.Code.AUTH_FAILURE) { + return; + } + + // If there are any registrations, remove them and issue registration failure. + Collection<ObjectIdP> desiredRegistrations = registrationManager.removeRegisteredObjects(); + logger.warning("Issuing failure for %s objects", desiredRegistrations.size()); + for (ObjectIdP objectId : desiredRegistrations) { + listener.informRegistrationFailure(this, + ProtoWrapperConverter.convertFromObjectIdProto(objectId), false, + "Auth error: " + description); + } + } + + /** + * Returns whether the token in the header of {@code parsedMessage} matches either the + * client token or nonce of this Ticl (depending on which is non-{@code null}). + */ + private boolean validateToken(ParsedMessage parsedMessage) { + if (clientToken != null) { + // Client token case. + if (!TypedUtil.<Bytes>equals(clientToken, parsedMessage.header.token)) { + logger.info("Incoming message has bad token: server = %s, client = %s", + parsedMessage.header.token, clientToken); + statistics.recordError(ClientErrorType.TOKEN_MISMATCH); + return false; + } + return true; + } else if (nonce != null) { + // Nonce case. + if (!TypedUtil.<Bytes>equals(nonce, parsedMessage.header.token)) { + statistics.recordError(ClientErrorType.NONCE_MISMATCH); + logger.info("Rejecting server message with mismatched nonce: Client = %s, Server = %s", + nonce, parsedMessage.header.token); + return false; + } else { + logger.info("Accepting server message with matching nonce: %s", nonce); + return true; + } + } + // Neither token nor nonce; ignore message. + logger.warning("Neither token nor nonce was set in validateToken: %s, %s", clientToken, nonce); + return false; + } + + /** + * Requests a new client identifier from the server. + * <p> + * REQUIRES: no token currently be held. + * + * @param debugString information to identify the caller + */ + private void acquireToken(final String debugString) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + + // Clear the current token and schedule the token acquisition. + setClientToken(null); + acquireTokenTask.ensureScheduled(debugString); + } + + /** + * Sends an info message to the server. If {@code mustSendPerformanceCounters} is true, + * the performance counters are sent regardless of when they were sent earlier. + */ + private void sendInfoMessageToServer(boolean mustSendPerformanceCounters, + boolean requestServerSummary) { + logger.info("Sending info message to server; request server summary = %s", + requestServerSummary); + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + + List<SimplePair<String, Integer>> performanceCounters = + new ArrayList<SimplePair<String, Integer>>(); + ClientConfigP configToSend = null; + if (mustSendPerformanceCounters) { + statistics.getNonZeroStatistics(performanceCounters); + configToSend = config; + } + protocolHandler.sendInfoMessage(performanceCounters, configToSend, requestServerSummary, + batchingTask); + } + + /** Reads the Ticl state from persistent storage (if any) and calls {@code startInternal}. */ + private void scheduleStartAfterReadingStateBlob() { + storage.readKey(CLIENT_TOKEN_KEY, new Callback<SimplePair<Status, byte[]>>() { + @Override + public void accept(final SimplePair<Status, byte[]> readResult) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + final byte[] serializedState = readResult.getFirst().isSuccess() ? + readResult.getSecond() : null; + // Call start now. + if (!readResult.getFirst().isSuccess()) { + statistics.recordError(ClientErrorType.PERSISTENT_READ_FAILURE); + logger.warning("Could not read state blob: %s", readResult.getFirst().getMessage()); + } + startInternal(serializedState); + } + }); + } + + /** + * Converts an operation type {@code regOpType} to a + * {@code InvalidationListener.RegistrationState}. + */ + private static InvalidationListener.RegistrationState convertOpTypeToRegState(int regOpType) { + InvalidationListener.RegistrationState regState = + regOpType == RegistrationP.OpType.REGISTER ? + InvalidationListener.RegistrationState.REGISTERED : + InvalidationListener.RegistrationState.UNREGISTERED; + return regState; + } + + /** + * Sets the nonce to {@code newNonce}. + * <p> + * REQUIRES: {@code newNonce} be null or {@link #clientToken} be null. + * The goal is to ensure that a nonce is never set unless there is no + * client token, unless the nonce is being cleared. + */ + private void setNonce(Bytes newNonce) { + if ((newNonce != null) && (clientToken != null)) { + throw new IllegalStateException("Tried to set nonce with existing token " + clientToken); + } + this.nonce = newNonce; + } + + /** + * Returns a randomly generated nonce. Visible for testing only. + */ + + static Bytes generateNonce(Random random) { + // Generate 8 random bytes. + byte[] randomBytes = new byte[8]; + random.nextBytes(randomBytes); + return new Bytes(randomBytes); + } + + /** + * Sets the clientToken to {@code newClientToken}. + * <p> + * REQUIRES: {@code newClientToken} be null or {@link #nonce} be null. + * The goal is to ensure that a token is never set unless there is no + * nonce, unless the token is being cleared. + */ + private void setClientToken(Bytes newClientToken) { + if ((newClientToken != null) && (nonce != null)) { + throw new IllegalStateException("Tried to set token with existing nonce " + nonce); + } + + // If the ticl is in the process of being started and we are getting a new token (either from + // persistence or from the server, start the ticl and inform the application. + boolean finishStartingTicl = !ticlState.isStarted() && + (clientToken == null) && (newClientToken != null); + this.clientToken = newClientToken; + + if (finishStartingTicl) { + finishStartingTiclAndInformListener(); + } + } + + /** Start the ticl and inform the listener that it is ready. */ + private void finishStartingTiclAndInformListener() { + Preconditions.checkState(!ticlState.isStarted()); + ticlState.start(); + listener.ready(this); + + // We are not currently persisting our registration digest, so regardless of whether or not + // we are restarting from persistent state, we need to query the application for all of + // its registrations. + listener.reissueRegistrations(InvalidationClientCore.this, RegistrationManager.EMPTY_PREFIX, 0); + logger.info("Ticl started: %s", this); + } + + /** + * Returns an exponential backoff generator with {@code initialDelayMs} and other state as + * given in {@code marshalledState}. + */ + private TiclExponentialBackoffDelayGenerator createExpBackOffGenerator(int initialDelayMs, + ExponentialBackoffState marshalledState) { + if (marshalledState != null) { + return new TiclExponentialBackoffDelayGenerator(random, initialDelayMs, + config.getMaxExponentialBackoffFactor(), marshalledState); + } else { + return new TiclExponentialBackoffDelayGenerator(random, initialDelayMs, + config.getMaxExponentialBackoffFactor()); + } + } + + /** Returns a map from recurring task name to the runnable for that recurring task. */ + protected Map<String, Runnable> getRecurringTasks() { + final int numPersistentTasks = 6; + HashMap<String, Runnable> tasks = new HashMap<String, Runnable>(numPersistentTasks); + tasks.put(AcquireTokenTask.TASK_NAME, acquireTokenTask.getRunnable()); + tasks.put(RegSyncHeartbeatTask.TASK_NAME, regSyncHeartbeatTask.getRunnable()); + tasks.put(PersistentWriteTask.TASK_NAME, persistentWriteTask.getRunnable()); + tasks.put(HeartbeatTask.TASK_NAME, heartbeatTask.getRunnable()); + tasks.put(BatchingTask.TASK_NAME, batchingTask.getRunnable()); + tasks.put(InitialPersistentHeartbeatTask.TASK_NAME, + initialPersistentHeartbeatTask.getRunnable()); + return tasks; + } + + @Override + public void toCompactString(TextBuilder builder) { + builder.append("Client: ").append(applicationClientId).append(", ") + .append(clientToken).append(", ").append(ticlState); + } + + @Override + public InvalidationClientState marshal() { + Preconditions.checkState(internalScheduler.isRunningOnThread(), + "Not running on internal thread"); + InvalidationClientState.Builder builder = new InvalidationClientState.Builder(); + builder.runState = ticlState.marshal(); + builder.clientToken = clientToken; + builder.nonce = nonce; + builder.shouldSendRegistrations = shouldSendRegistrations; + builder.lastMessageSendTimeMs = lastMessageSendTimeMs; + builder.isOnline = isOnline; + builder.protocolHandlerState = protocolHandler.marshal(); + builder.registrationManagerState = registrationManager.marshal(); + builder.acquireTokenTaskState = acquireTokenTask.marshal(); + builder.regSyncHeartbeatTaskState = regSyncHeartbeatTask.marshal(); + builder.persistentWriteTaskState = persistentWriteTask.marshal(); + builder.heartbeatTaskState = heartbeatTask.marshal(); + builder.batchingTaskState = batchingTask.marshal(); + builder.lastWrittenState = persistentWriteTask.lastWrittenState.get(); + builder.statisticsState = statistics.marshal(); + return builder.build(); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/InvalidationClientImpl.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/InvalidationClientImpl.java new file mode 100644 index 0000000..4ba7337 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/InvalidationClientImpl.java @@ -0,0 +1,163 @@ +/* + * 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; + +import static com.google.ipc.invalidation.external.client.SystemResources.Scheduler.NO_DELAY; + +import com.google.ipc.invalidation.external.client.InvalidationListener; +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; + +import java.util.Collection; +import java.util.Random; + +/** + * Implementation of the standard Java Ticl. This class extends {@link InvalidationClientCore} + * with additional thread-safety related constructs. Specifically, it ensures that: + * <p> + * 1. All application calls into the Ticl execute on the internal scheduler. + * <p> + * 2. The storage layer always executes callbacks on the internal scheduler thread. + * <p> + * 3. All calls into the listener are made on the listener scheduler thread. + */ +public class InvalidationClientImpl extends InvalidationClientCore { + public InvalidationClientImpl(final SystemResources resources, Random random, int clientType, + final byte[] clientName, ClientConfigP config, String applicationName, + InvalidationListener listener) { + super( + // We will make Storage a SafeStorage after the constructor call. It's not possible to + // construct a new resources around the existing components and pass that to super(...) + // because then subsequent calls on the first resources object (e.g., start) would not + // affect the new resources object that the Ticl would be using. + resources, + + // Pass basic parameters through unmodified. + random, clientType, clientName, config, applicationName, + + // Wrap the listener in a CheckingInvalidationListener to enforce appropriate threading. + new CheckingInvalidationListener(listener, + resources.getInternalScheduler(), resources.getListenerScheduler(), + resources.getLogger()) + ); // End super. + + // Make Storage safe. + this.storage = new SafeStorage(resources.getStorage()); + this.storage.setSystemResources(resources); + + // CheckingInvalidationListener needs the statistics object created by our super() call, so + // we can't provide it at construction-time (since it hasn't been created yet). + ((CheckingInvalidationListener) this.listener).setStatistics(statistics); + + } + + // Methods below are public methods from InvalidationClient that must first enqueue onto the + // internal thread. + + @Override + public void start() { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.start(); + } + }); + } + + @Override + public void stop() { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.stop(); + } + }); + } + + @Override + public void register(final ObjectId objectId) { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.register(objectId); + } + }); + } + + @Override + public void register(final Collection<ObjectId> objectIds) { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.register(objectIds); + } + }); + } + + @Override + public void unregister(final ObjectId objectId) { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.unregister(objectId); + } + }); + } + + @Override + public void unregister(final Collection<ObjectId> objectIds) { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.unregister(objectIds); + } + }); + } + + @Override + public void acknowledge(final AckHandle ackHandle) { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.acknowledge(ackHandle); + } + }); + } + + // End InvalidationClient methods. + + @Override // InvalidationClientCore; overriding to add concurrency control. + void handleIncomingMessage(final byte[] message) { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.handleIncomingMessage(message); + } + }); + } + + @Override // InvalidationClientCore; overriding to add concurrency control. + public void handleNetworkStatusChange(final boolean isOnline) { + getResources().getInternalScheduler().schedule(NO_DELAY, new Runnable() { + @Override + public void run() { + InvalidationClientImpl.super.handleNetworkStatusChange(isOnline); + } + }); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/MemoryStorageImpl.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/MemoryStorageImpl.java new file mode 100644 index 0000000..1b2a93a --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/MemoryStorageImpl.java @@ -0,0 +1,146 @@ +/* + * 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; + +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.external.client.SystemResources.Storage; +import com.google.ipc.invalidation.external.client.types.Callback; +import com.google.ipc.invalidation.external.client.types.SimplePair; +import com.google.ipc.invalidation.external.client.types.Status; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.InternalBase; +import com.google.ipc.invalidation.util.NamedRunnable; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.ipc.invalidation.util.TypedUtil; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Map-based in-memory implementation of {@link Storage}. + * + */ +public class MemoryStorageImpl extends InternalBase implements Storage { + private Scheduler scheduler; + private Map<String, byte[]> ticlPersistentState = new HashMap<String, byte[]>(); + + @Override + public void writeKey(final String key, final byte[] value, final Callback<Status> callback) { + // Need to schedule immediately because C++ locks aren't reentrant, and + // C++ locking code assumes that this call will not return directly. + + // Schedule the write even if the resources are started since the + // scheduler will prevent it from running in case the resources have been + // stopped. + scheduler.schedule(Scheduler.NO_DELAY, + new NamedRunnable("MemoryStorage.writeKey") { + @Override + public void run() { + ticlPersistentState.put(key, value); + callback.accept(Status.newInstance(Status.Code.SUCCESS, "")); + } + }); + } + + int numKeysForTest() { + return ticlPersistentState.size(); + } + + @Override + public void setSystemResources(SystemResources resources) { + this.scheduler = resources.getInternalScheduler(); + } + + @Override + public void readKey(final String key, final Callback<SimplePair<Status, byte[]>> done) { + scheduler.schedule(Scheduler.NO_DELAY, + new NamedRunnable("MemoryStorage.readKey") { + @Override + public void run() { + byte[] value = TypedUtil.mapGet(ticlPersistentState, key); + final SimplePair<Status, byte[]> result; + if (value != null) { + result = SimplePair.of(Status.newInstance(Status.Code.SUCCESS, ""), value); + } else { + String error = "No value present in map for " + key; + result = SimplePair.of(Status.newInstance(Status.Code.PERMANENT_FAILURE, error), null); + } + done.accept(result); + } + }); + } + + @Override + public void deleteKey(final String key, final Callback<Boolean> done) { + scheduler.schedule(Scheduler.NO_DELAY, + new NamedRunnable("MemoryStorage.deleteKey") { + @Override + public void run() { + TypedUtil.remove(ticlPersistentState, key); + done.accept(true); + } + }); + } + + @Override + public void readAllKeys(final Callback<SimplePair<Status, String>> done) { + scheduler.schedule(Scheduler.NO_DELAY, + new NamedRunnable("MemoryStorage.readAllKeys") { + @Override + public void run() { + Status successStatus = Status.newInstance(Status.Code.SUCCESS, ""); + for (String key : ticlPersistentState.keySet()) { + done.accept(SimplePair.of(successStatus, key)); + } + done.accept(null); + } + }); + } + + /** + * Same as write except without any callbacks and is NOT done on the internal thread. + * Test code should typically call this before starting the client. + */ + void writeForTest(final String key, final byte[] value) { + ticlPersistentState.put(key, value); + } + + /** + * Sets the scheduler, for tests. The Android tests use this to supply a scheduler that executes + * no-delay items in-line. + */ + public void setSchedulerForTest(Scheduler newScheduler) { + scheduler = newScheduler; + } + + /** + * Same as read except without any callbacks and is NOT done on the internal thread. + */ + public byte[] readForTest(final String key) { + return ticlPersistentState.get(key); + } + + @Override + public void toCompactString(TextBuilder builder) { + builder.append("Storage state: "); + for (Map.Entry<String, byte[]> entry : ticlPersistentState.entrySet()) { + builder.appendFormat("<%s, %s>, ", entry.getKey(), Bytes.toString(entry.getValue())); + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/PersistenceUtils.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/PersistenceUtils.java new file mode 100644 index 0000000..be4fe21 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/PersistenceUtils.java @@ -0,0 +1,76 @@ +/* + * 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; + +import com.google.ipc.invalidation.common.DigestFunction; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.ticl.proto.Client.PersistentStateBlob; +import com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.TypedUtil; + +/** + * Utility methods for handling the Ticl persistent state. + * + */ +public class PersistenceUtils { + + /** Serializes a Ticl state blob. */ + public static byte[] serializeState( + PersistentTiclState state, DigestFunction digestFn) { + Bytes mac = generateMac(state, digestFn); + return PersistentStateBlob.create(state, mac).toByteArray(); + } + + /** + * Deserializes a Ticl state blob. Returns either the parsed state or {@code null} + * if it could not be parsed. + */ + public static PersistentTiclState deserializeState(Logger logger, byte[] stateBlobBytes, + DigestFunction digestFn) { + PersistentStateBlob stateBlob; + try { + // Try parsing the envelope protocol buffer. + stateBlob = PersistentStateBlob.parseFrom(stateBlobBytes); + } catch (ValidationException exception) { + logger.severe("Failed deserializing Ticl state: %s", exception.getMessage()); + return null; + } + + // Check the mac in the envelope against the recomputed mac from the state. + PersistentTiclState ticlState = stateBlob.getTiclState(); + Bytes mac = generateMac(ticlState, digestFn); + if (!TypedUtil.<Bytes>equals(mac, stateBlob.getAuthenticationCode())) { + logger.warning("Ticl state failed MAC check: computed %s vs %s", mac, + stateBlob.getAuthenticationCode()); + return null; + } + return ticlState; + } + + /** Returns a message authentication code over {@code state}. */ + private static Bytes generateMac(PersistentTiclState state, DigestFunction digestFn) { + digestFn.reset(); + digestFn.update(state.toByteArray()); + return new Bytes(digestFn.getDigest()); + } + + private PersistenceUtils() { + // Prevent instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/ProtoWrapperConverter.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/ProtoWrapperConverter.java new file mode 100644 index 0000000..79f5f18 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/ProtoWrapperConverter.java @@ -0,0 +1,113 @@ +/* + * 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; + +import com.google.ipc.invalidation.external.client.types.Invalidation; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.ticl.proto.CommonProtos; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Utilities to convert between {@link com.google.ipc.invalidation.util.ProtoWrapper ProtoWrapper} + * wrappers and externally-exposed types in the Ticl. + */ +public class ProtoWrapperConverter { + + /** + * Converts an object id protocol buffer {@code objectId} to the + * corresponding external type and returns it. + */ + public static ObjectId convertFromObjectIdProto(ObjectIdP objectIdProto) { + Preconditions.checkNotNull(objectIdProto); + return ObjectId.newInstance(objectIdProto.getSource(), objectIdProto.getName().getByteArray()); + } + + /** + * Converts an object id {@code objectId} to the corresponding protocol buffer + * and returns it. + */ + public static ObjectIdP convertToObjectIdProto(ObjectId objectId) { + Preconditions.checkNotNull(objectId); + return ObjectIdP.create(objectId.getSource(), new Bytes(objectId.getName())); + } + + /** + * Returns a list of {@link ObjectIdP} by converting each element of {@code objectIds} to + * an {@code ObjectIdP}. + */ + public static Collection<ObjectIdP> convertToObjectIdProtoCollection( + Iterable<ObjectId> objectIds) { + int expectedSize = (objectIds instanceof Collection) ? ((Collection<?>) objectIds).size() : 1; + ArrayList<ObjectIdP> objectIdPs = new ArrayList<ObjectIdP>(expectedSize); + for (ObjectId objectId : objectIds) { + objectIdPs.add(convertToObjectIdProto(objectId)); + } + return objectIdPs; + } + + /** + * Returns a list of {@link ObjectId} by converting each element of {@code objectIdProtos} to + * an {@code ObjectId}. + */ + public static Collection<ObjectId> convertFromObjectIdProtoCollection( + Collection<ObjectIdP> objectIdProtos) { + ArrayList<ObjectId> objectIds = new ArrayList<ObjectId>(objectIdProtos.size()); + for (ObjectIdP objectIdProto : objectIdProtos) { + objectIds.add(convertFromObjectIdProto(objectIdProto)); + } + return objectIds; + } + + /** + * Converts an invalidation protocol buffer {@code invalidation} to the + * corresponding external object and returns it + */ + public static Invalidation convertFromInvalidationProto(InvalidationP invalidation) { + Preconditions.checkNotNull(invalidation); + ObjectId objectId = convertFromObjectIdProto(invalidation.getObjectId()); + + // No bridge arrival time in invalidation. + return Invalidation.newInstance(objectId, invalidation.getVersion(), + invalidation.hasPayload() ? invalidation.getPayload().getByteArray() : null, + invalidation.getIsTrickleRestart()); + } + + /** + * Converts an invalidation {@code invalidation} to the corresponding protocol + * buffer and returns it. + */ + public static InvalidationP convertToInvalidationProto(Invalidation invalidation) { + Preconditions.checkNotNull(invalidation); + ObjectIdP objectId = convertToObjectIdProto(invalidation.getObjectId()); + + // Invalidations clients do not know about trickle restarts. Every invalidation is allowed + // to suppress earlier invalidations and acks implicitly acknowledge all previous + // invalidations. Therefore the correct semanantics are provided by setting isTrickleRestart to + // true. + return CommonProtos.newInvalidationP(objectId, invalidation.getVersion(), + invalidation.getIsTrickleRestartForInternalUse(), invalidation.getPayload()); + } + + private ProtoWrapperConverter() { // To prevent instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/ProtocolHandler.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/ProtocolHandler.java new file mode 100644 index 0000000..131de39 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/ProtocolHandler.java @@ -0,0 +1,661 @@ +/* + * 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; + +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.SystemResources.NetworkChannel; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.external.client.types.SimplePair; +import com.google.ipc.invalidation.ticl.InvalidationClientCore.BatchingTask; +import com.google.ipc.invalidation.ticl.Statistics.ClientErrorType; +import com.google.ipc.invalidation.ticl.Statistics.ReceivedMessageType; +import com.google.ipc.invalidation.ticl.Statistics.SentMessageType; +import com.google.ipc.invalidation.ticl.proto.ClientConstants; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientHeader; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientToServerMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientVersion; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ConfigChangeMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ErrorMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoRequestMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage.DigestSerializationType; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP.OpType; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatusMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncRequestMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerToClientMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.TokenControlMessage; +import com.google.ipc.invalidation.ticl.proto.CommonProtos; +import com.google.ipc.invalidation.ticl.proto.JavaClient.BatcherState; +import com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.InternalBase; +import com.google.ipc.invalidation.util.Marshallable; +import com.google.ipc.invalidation.util.Preconditions; +import com.google.ipc.invalidation.util.ProtoWrapper; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.Smearer; +import com.google.ipc.invalidation.util.TextBuilder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * A layer for interacting with low-level protocol messages. Parses messages from the server and + * calls appropriate functions on the {@code ProtocolListener} to handle various types of message + * content. Also buffers message data from the client and constructs and sends messages to the + * server. + * <p> + * This class implements {@link Marshallable}, so its state can be written to a protocol buffer, + * and instances can be restored from such protocol buffers. Additionally, the nested class + * {@link Batcher} also implements {@code Marshallable} for the same reason. + * <p> + * Note that while we talk about "marshalling," in this context we mean marshalling to protocol + * buffers, not raw bytes. + * + */ +class ProtocolHandler implements Marshallable<ProtocolHandlerState> { + /** Class that batches messages to the server. */ + private static class Batcher implements Marshallable<BatcherState> { + /** Statistics to be updated when messages are created. */ + private final Statistics statistics; + + /** Resources used for logging and thread assertions. */ + private final SystemResources resources; + + /** Set of pending registrations stored as a map for overriding later operations. */ + private final Map<ObjectIdP, Integer> pendingRegistrations = new HashMap<ObjectIdP, Integer>(); + + /** Set of pending invalidation acks. */ + private final Set<InvalidationP> pendingAckedInvalidations = new HashSet<InvalidationP>(); + + /** Set of pending registration sub trees for registration sync. */ + private final Set<RegistrationSubtree> pendingRegSubtrees = new HashSet<RegistrationSubtree>(); + + /** Pending initialization message to send to the server, if any. */ + private InitializeMessage pendingInitializeMessage = null; + + /** Pending info message to send to the server, if any. */ + private InfoMessage pendingInfoMessage = null; + + /** Creates a batcher. */ + Batcher(SystemResources resources, Statistics statistics) { + this.resources = resources; + this.statistics = statistics; + } + + /** Creates a batcher from {@code marshalledState}. */ + Batcher(SystemResources resources, Statistics statistics, BatcherState marshalledState) { + this(resources, statistics); + for (ObjectIdP registration : marshalledState.getRegistration()) { + pendingRegistrations.put(registration, RegistrationP.OpType.REGISTER); + } + for (ObjectIdP unregistration : marshalledState.getUnregistration()) { + pendingRegistrations.put(unregistration, RegistrationP.OpType.UNREGISTER); + } + for (InvalidationP ack : marshalledState.getAcknowledgement()) { + pendingAckedInvalidations.add(ack); + } + for (RegistrationSubtree subtree : marshalledState.getRegistrationSubtree()) { + pendingRegSubtrees.add(subtree); + } + pendingInitializeMessage = marshalledState.getNullableInitializeMessage(); + if (marshalledState.hasInfoMessage()) { + pendingInfoMessage = marshalledState.getInfoMessage(); + } + } + + /** Sets the initialize message to be sent. */ + void setInitializeMessage(InitializeMessage msg) { + pendingInitializeMessage = msg; + } + + /** Sets the info message to be sent. */ + void setInfoMessage(InfoMessage msg) { + pendingInfoMessage = msg; + } + + /** Adds a registration on {@code oid} of {@code opType} to the registrations to be sent. */ + void addRegistration(ObjectIdP oid, Integer opType) { + pendingRegistrations.put(oid, opType); + } + + /** Adds {@code ack} to the set of acknowledgements to be sent. */ + void addAck(InvalidationP ack) { + pendingAckedInvalidations.add(ack); + } + + /** Adds {@code subtree} to the set of registration subtrees to be sent. */ + void addRegSubtree(RegistrationSubtree subtree) { + pendingRegSubtrees.add(subtree); + } + + /** + * Returns a builder for a {@link ClientToServerMessage} to be sent to the server. Crucially, + * the builder does <b>NOT</b> include the message header. + * @param hasClientToken whether the client currently holds a token + */ + ClientToServerMessage toMessage(final ClientHeader header, boolean hasClientToken) { + final InitializeMessage initializeMessage; + final RegistrationMessage registrationMessage; + final RegistrationSyncMessage registrationSyncMessage; + final InvalidationMessage invalidationAckMessage; + final InfoMessage infoMessage; + + if (pendingInitializeMessage != null) { + statistics.recordSentMessage(SentMessageType.INITIALIZE); + initializeMessage = pendingInitializeMessage; + pendingInitializeMessage = null; + } else { + initializeMessage = null; + } + + // Note: Even if an initialize message is being sent, we can send additional + // messages such as regisration messages, etc to the server. But if there is no token + // and an initialize message is not being sent, we cannot send any other message. + + if (!hasClientToken && (initializeMessage == null)) { + // Cannot send any message + resources.getLogger().warning( + "Cannot send message since no token and no initialize msg"); + statistics.recordError(ClientErrorType.TOKEN_MISSING_FAILURE); + return null; + } + + // Check for pending batched operations and add to message builder if needed. + + // Add reg, acks, reg subtrees - clear them after adding. + if (!pendingAckedInvalidations.isEmpty()) { + invalidationAckMessage = createInvalidationAckMessage(); + statistics.recordSentMessage(SentMessageType.INVALIDATION_ACK); + } else { + invalidationAckMessage = null; + } + + // Check regs. + if (!pendingRegistrations.isEmpty()) { + registrationMessage = createRegistrationMessage(); + statistics.recordSentMessage(SentMessageType.REGISTRATION); + } else { + registrationMessage = null; + } + + // Check reg substrees. + if (!pendingRegSubtrees.isEmpty()) { + // If there are multiple pending reg subtrees, only one is sent. + ArrayList<RegistrationSubtree> regSubtrees = new ArrayList<RegistrationSubtree>(1); + regSubtrees.add(pendingRegSubtrees.iterator().next()); + registrationSyncMessage = RegistrationSyncMessage.create(regSubtrees); + pendingRegSubtrees.clear(); + statistics.recordSentMessage(SentMessageType.REGISTRATION_SYNC); + } else { + registrationSyncMessage = null; + } + + // Check if an info message has to be sent. + if (pendingInfoMessage != null) { + statistics.recordSentMessage(SentMessageType.INFO); + infoMessage = pendingInfoMessage; + pendingInfoMessage = null; + } else { + infoMessage = null; + } + + return ClientToServerMessage.create(header, initializeMessage, registrationMessage, + registrationSyncMessage, invalidationAckMessage, infoMessage); + } + + /** + * Creates a registration message based on registrations from {@code pendingRegistrations} + * and returns it. + * <p> + * REQUIRES: pendingRegistrations.size() > 0 + */ + private RegistrationMessage createRegistrationMessage() { + Preconditions.checkState(!pendingRegistrations.isEmpty()); + + // Run through the pendingRegistrations map. + List<RegistrationP> pendingRegistrations = + new ArrayList<RegistrationP>(this.pendingRegistrations.size()); + for (Map.Entry<ObjectIdP, Integer> entry : this.pendingRegistrations.entrySet()) { + pendingRegistrations.add(RegistrationP.create(entry.getKey(), entry.getValue())); + } + this.pendingRegistrations.clear(); + return RegistrationMessage.create(pendingRegistrations); + } + + /** + * Creates an invalidation ack message based on acks from {@code pendingAckedInvalidations} and + * returns it. + * <p> + * REQUIRES: pendingAckedInvalidations.size() > 0 + */ + private InvalidationMessage createInvalidationAckMessage() { + Preconditions.checkState(!pendingAckedInvalidations.isEmpty()); + InvalidationMessage ackMessage = + InvalidationMessage.create(new ArrayList<InvalidationP>(pendingAckedInvalidations)); + pendingAckedInvalidations.clear(); + return ackMessage; + } + + @Override + public BatcherState marshal() { + // Marshall (un)registrations. + ArrayList<ObjectIdP> registrations = new ArrayList<ObjectIdP>(pendingRegistrations.size()); + ArrayList<ObjectIdP> unregistrations = new ArrayList<ObjectIdP>(pendingRegistrations.size()); + for (Map.Entry<ObjectIdP, Integer> entry : pendingRegistrations.entrySet()) { + Integer opType = entry.getValue(); + ObjectIdP oid = entry.getKey(); + new ArrayList<ObjectIdP>(pendingRegistrations.size()); + switch (opType) { + case OpType.REGISTER: + registrations.add(oid); + break; + case OpType.UNREGISTER: + unregistrations.add(oid); + break; + default: + throw new IllegalArgumentException(opType.toString()); + } + } + return BatcherState.create(registrations, unregistrations, pendingAckedInvalidations, + pendingRegSubtrees, pendingInitializeMessage, pendingInfoMessage); + } + } + + /** Representation of a message header for use in a server message. */ + static class ServerMessageHeader extends InternalBase { + /** + * Constructs an instance. + * + * @param token server-sent token + * @param registrationSummary summary over server registration state + */ + ServerMessageHeader(Bytes token, RegistrationSummary registrationSummary) { + this.token = token; + this.registrationSummary = registrationSummary; + } + + /** Server-sent token. */ + Bytes token; + + /** Summary of the client's registration state at the server. */ + RegistrationSummary registrationSummary; + + @Override + public void toCompactString(TextBuilder builder) { + builder.appendFormat("Token: %s, Summary: %s", token, registrationSummary); + } + } + + /** + * Representation of a message receiver for the server. Such a message is guaranteed to be + * valid, but the session token is <b>not</b> checked. + */ + static class ParsedMessage { + /* + * Each of these fields corresponds directly to a field in the ServerToClientMessage protobuf. + * It is non-null iff the corresponding hasYYY method in the protobuf would return true. + */ + final ServerMessageHeader header; + final TokenControlMessage tokenControlMessage; + final InvalidationMessage invalidationMessage; + final RegistrationStatusMessage registrationStatusMessage; + final RegistrationSyncRequestMessage registrationSyncRequestMessage; + final ConfigChangeMessage configChangeMessage; + final InfoRequestMessage infoRequestMessage; + final ErrorMessage errorMessage; + + /** Constructs an instance from a {@code rawMessage}. */ + ParsedMessage(ServerToClientMessage rawMessage) { + // For each field, assign it to the corresponding protobuf field if present, else null. + ServerHeader messageHeader = rawMessage.getHeader(); + header = new ServerMessageHeader(messageHeader.getClientToken(), + messageHeader.getNullableRegistrationSummary()); + tokenControlMessage = + rawMessage.hasTokenControlMessage() ? rawMessage.getTokenControlMessage() : null; + invalidationMessage = rawMessage.getNullableInvalidationMessage(); + registrationStatusMessage = rawMessage.getNullableRegistrationStatusMessage(); + registrationSyncRequestMessage = rawMessage.hasRegistrationSyncRequestMessage() + ? rawMessage.getRegistrationSyncRequestMessage() : null; + configChangeMessage = + rawMessage.hasConfigChangeMessage() ? rawMessage.getConfigChangeMessage() : null; + infoRequestMessage = rawMessage.getNullableInfoRequestMessage(); + errorMessage = rawMessage.getNullableErrorMessage(); + } + } + + /** + * Listener for protocol events. The handler guarantees that the call will be made on the internal + * thread that the SystemResources provides. + */ + interface ProtocolListener { + /** Records that a message was sent to the server at the current time. */ + void handleMessageSent(); + + /** Returns a summary of the current desired registrations. */ + RegistrationSummary getRegistrationSummary(); + + /** Returns the current server-assigned client token, if any. */ + Bytes getClientToken(); + } + + /** Information about the client, e.g., application name, OS, etc. */ + private final ClientVersion clientVersion; + + /** A logger. */ + private final Logger logger; + + /** Scheduler for the client's internal processing. */ + private final Scheduler internalScheduler; + + /** Network channel for sending and receiving messages to and from the server. */ + private final NetworkChannel network; + + /** The protocol listener. */ + private final ProtocolListener listener; + + /** Batches messages to the server. */ + private final Batcher batcher; + + /** A debug message id that is added to every message to the server. */ + private int messageId = 1; + + // State specific to a client. If we want to support multiple clients, this could + // be in a map or could be eliminated (e.g., no batching). + + /** The last known time from the server. */ + private long lastKnownServerTimeMs = 0; + + /** + * The next time before which a message cannot be sent to the server. If this is less than current + * time, a message can be sent at any time. + */ + private long nextMessageSendTimeMs = 0; + + /** Statistics objects to track number of sent messages, etc. */ + private final Statistics statistics; + + /** Client type for inclusion in headers. */ + private final int clientType; + + /** + * Creates an instance. + * + * @param config configuration for the client + * @param resources resources to use + * @param smearer a smearer to randomize delays + * @param statistics track information about messages sent/received, etc + * @param applicationName name of the application using the library (for debugging/monitoring) + * @param listener callback for protocol events + */ + ProtocolHandler(ProtocolHandlerConfigP config, final SystemResources resources, + Smearer smearer, Statistics statistics, int clientType, String applicationName, + ProtocolListener listener, ProtocolHandlerState marshalledState) { + this.logger = resources.getLogger(); + this.statistics = statistics; + this.internalScheduler = resources.getInternalScheduler(); + this.network = resources.getNetwork(); + this.listener = listener; + this.clientVersion = CommonProtos.newClientVersion(resources.getPlatform(), "Java", + applicationName); + this.clientType = clientType; + if (marshalledState == null) { + // If there is no marshalled state, construct a clean batcher. + this.batcher = new Batcher(resources, statistics); + } else { + // Otherwise, restore the batcher from the marshalled state. + this.batcher = new Batcher(resources, statistics, marshalledState.getBatcherState()); + this.messageId = marshalledState.getMessageId(); + this.lastKnownServerTimeMs = marshalledState.getLastKnownServerTimeMs(); + this.nextMessageSendTimeMs = marshalledState.getNextMessageSendTimeMs(); + } + logger.info("Created protocol handler for application %s, platform %s", applicationName, + resources.getPlatform()); + } + + /** Returns a default config for the protocol handler. */ + static ProtocolHandlerConfigP createConfig() { + // Allow at most 3 messages every 5 seconds. + int windowMs = 5 * 1000; + int numMessagesPerWindow = 3; + + List<RateLimitP> rateLimits = new ArrayList<RateLimitP>(); + rateLimits.add(RateLimitP.create(windowMs, numMessagesPerWindow)); + return ProtocolHandlerConfigP.create(null, rateLimits); + } + + /** Returns a configuration object with parameters set for unit tests. */ + static ProtocolHandlerConfigP createConfigForTest() { + // No rate limits + int smallBatchDelayForTest = 200; + return ProtocolHandlerConfigP.create(smallBatchDelayForTest, new ArrayList<RateLimitP>(0)); + } + + /** + * Returns the next time a message is allowed to be sent to the server. Typically, this will be + * in the past, meaning that the client is free to send a message at any time. + */ + public long getNextMessageSendTimeMsForTest() { + return nextMessageSendTimeMs; + } + + /** + * Handles a message from the server. If the message can be processed (i.e., is valid, is + * of the right version, and is not a silence message), returns a {@link ParsedMessage} + * representing it. Otherwise, returns {@code null}. + * <p> + * This class intercepts and processes silence messages. In this case, it will discard any other + * data in the message. + * <p> + * Note that this method does <b>not</b> check the session token of any message. + */ + ParsedMessage handleIncomingMessage(byte[] incomingMessage) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + ServerToClientMessage message; + try { + message = ServerToClientMessage.parseFrom(incomingMessage); + } catch (ValidationException exception) { + statistics.recordError(ClientErrorType.INCOMING_MESSAGE_FAILURE); + logger.warning("Incoming message is invalid: %s", Bytes.toLazyCompactString(incomingMessage)); + return null; + } + + // Check the version of the message. + if (message.getHeader().getProtocolVersion().getVersion().getMajorVersion() != + ClientConstants.PROTOCOL_MAJOR_VERSION) { + statistics.recordError(ClientErrorType.PROTOCOL_VERSION_FAILURE); + logger.severe("Dropping message with incompatible version: %s", message); + return null; + } + + // Check if it is a ConfigChangeMessage which indicates that messages should no longer be + // sent for a certain duration. Perform this check before the token is even checked. + if (message.hasConfigChangeMessage()) { + ConfigChangeMessage configChangeMsg = message.getConfigChangeMessage(); + statistics.recordReceivedMessage(ReceivedMessageType.CONFIG_CHANGE); + if (configChangeMsg.hasNextMessageDelayMs()) { // Validator has ensured that it is positive. + nextMessageSendTimeMs = + internalScheduler.getCurrentTimeMs() + configChangeMsg.getNextMessageDelayMs(); + } + return null; // Ignore all other messages in the envelope. + } + + lastKnownServerTimeMs = Math.max(lastKnownServerTimeMs, message.getHeader().getServerTimeMs()); + return new ParsedMessage(message); + } + + /** + * Sends a message to the server to request a client token. + * + * @param applicationClientId application-specific client id + * @param nonce nonce for the request + * @param debugString information to identify the caller + */ + void sendInitializeMessage(ApplicationClientIdP applicationClientId, Bytes nonce, + BatchingTask batchingTask, String debugString) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + if (applicationClientId.getClientType() != clientType) { + // This condition is not fatal, but it probably represents a bug somewhere if it occurs. + logger.warning( + "Client type in application id does not match constructor-provided type: %s vs %s", + applicationClientId, clientType); + } + + // Simply store the message in pendingInitializeMessage and send it when the batching task runs. + InitializeMessage initializeMsg = InitializeMessage.create(clientType, nonce, + applicationClientId, DigestSerializationType.BYTE_BASED); + batcher.setInitializeMessage(initializeMsg); + logger.info("Batching initialize message for client: %s, %s", debugString, initializeMsg); + batchingTask.ensureScheduled(debugString); + } + + /** + * Sends an info message to the server with the performance counters supplied + * in {@code performanceCounters} and the config supplies in + * {@code configParams}. + * + * @param requestServerRegistrationSummary indicates whether to request the + * server's registration summary + */ + void sendInfoMessage(List<SimplePair<String, Integer>> performanceCounters, + ClientConfigP clientConfig, boolean requestServerRegistrationSummary, + BatchingTask batchingTask) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + + List<PropertyRecord> performanceCounterRecords = + new ArrayList<PropertyRecord>(performanceCounters.size()); + for (SimplePair<String, Integer> counter : performanceCounters) { + performanceCounterRecords.add(PropertyRecord.create(counter.first, counter.second)); + } + InfoMessage infoMessage = InfoMessage.create(clientVersion, /* configParameter */ null, + performanceCounterRecords, requestServerRegistrationSummary, clientConfig); + + // Simply store the message in pendingInfoMessage and send it when the batching task runs. + batcher.setInfoMessage(infoMessage); + batchingTask.ensureScheduled("Send-info"); + } + + /** + * Sends a registration request to the server. + * + * @param objectIds object ids on which to (un)register + * @param regOpType whether to register or unregister + */ + void sendRegistrations(Collection<ObjectIdP> objectIds, Integer regOpType, + BatchingTask batchingTask) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + for (ObjectIdP objectId : objectIds) { + batcher.addRegistration(objectId, regOpType); + } + batchingTask.ensureScheduled("Send-registrations"); + } + + /** Sends an acknowledgement for {@code invalidation} to the server. */ + void sendInvalidationAck(InvalidationP invalidation, BatchingTask batchingTask) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + // We could summarize acks when there are suppressing invalidations - we don't since it is + // unlikely to be too beneficial here. + logger.fine("Sending ack for invalidation %s", invalidation); + batcher.addAck(invalidation); + batchingTask.ensureScheduled("Send-Ack"); + } + + /** + * Sends a single registration subtree to the server. + * + * @param regSubtree subtree to send + */ + void sendRegistrationSyncSubtree(RegistrationSubtree regSubtree, BatchingTask batchingTask) { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + batcher.addRegSubtree(regSubtree); + logger.info("Adding subtree: %s", regSubtree); + batchingTask.ensureScheduled("Send-reg-sync"); + } + + /** Sends pending data to the server (e.g., registrations, acks, registration sync messages). */ + void sendMessageToServer() { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + if (nextMessageSendTimeMs > internalScheduler.getCurrentTimeMs()) { + logger.warning("In quiet period: not sending message to server: %s > %s", + nextMessageSendTimeMs, internalScheduler.getCurrentTimeMs()); + return; + } + + // Create the message from the batcher. + ClientToServerMessage message; + try { + message = batcher.toMessage(createClientHeader(), listener.getClientToken() != null); + if (message == null) { + // Happens when we don't have a token and are not sending an initialize message. Logged + // in batcher.toMessage(). + return; + } + } catch (ProtoWrapper.ValidationArgumentException exception) { + logger.severe("Tried to send invalid message: %s", batcher); + statistics.recordError(ClientErrorType.OUTGOING_MESSAGE_FAILURE); + return; + } + ++messageId; + + statistics.recordSentMessage(SentMessageType.TOTAL); + logger.fine("Sending message to server: %s", message); + network.sendMessage(message.toByteArray()); + + // Record that the message was sent. We're invoking the listener directly, rather than + // scheduling a new work unit to do it. It would be safer to do a schedule, but that's hard to + // do in Android, we wrote this listener (it's InvalidationClientCore, so we know what it does), + // and it's the last line of this function. + listener.handleMessageSent(); + } + + /** Returns the header to include on a message to the server. */ + private ClientHeader createClientHeader() { + Preconditions.checkState(internalScheduler.isRunningOnThread(), "Not on internal thread"); + return ClientHeader.create(ClientConstants.PROTOCOL_VERSION, + listener.getClientToken(), listener.getRegistrationSummary(), + internalScheduler.getCurrentTimeMs(), lastKnownServerTimeMs, Integer.toString(messageId), + clientType); + } + + @Override + public ProtocolHandlerState marshal() { + return ProtocolHandlerState.create(messageId, lastKnownServerTimeMs, nextMessageSendTimeMs, + batcher.marshal()); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RecurringTask.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RecurringTask.java new file mode 100644 index 0000000..8973854 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RecurringTask.java @@ -0,0 +1,256 @@ +/* + * 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; + +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState; +import com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState; +import com.google.ipc.invalidation.util.ExponentialBackoffDelayGenerator; +import com.google.ipc.invalidation.util.InternalBase; +import com.google.ipc.invalidation.util.Marshallable; +import com.google.ipc.invalidation.util.NamedRunnable; +import com.google.ipc.invalidation.util.Preconditions; +import com.google.ipc.invalidation.util.Smearer; +import com.google.ipc.invalidation.util.TextBuilder; + + +/** + * An abstraction for scheduling recurring tasks. Combines idempotent scheduling and smearing with + * conditional retries and exponential backoff. Does not implement throttling. Designed to support a + * variety of use cases, including: + * + * <ul> + * <li>Idempotent scheduling, e.g., ensuring that a batching task is scheduled exactly once. + * <li>Recurring tasks, e.g., periodic heartbeats. + * <li>Retriable actions aimed at state change, e.g., sending initialization messages. + * </ul> + * Each instance of this class manages the state for a single task. Examples: + * + * <pre> + * batchingTask = new RecurringTask("Batching", scheduler, logger, smearer, null, + * batchingDelayMs, NO_DELAY) { + * @Override + * public boolean runTask() { + * throttle.fire(); + * return false; // don't reschedule. + * } + * }; + * heartbeatTask = new RecurringTask("Heartbeat", scheduler, logger, smearer, null, + * heartbeatDelayMs, NO_DELAY) { + * @Override + * public boolean runTask() { + * sendInfoMessageToServer(false, !registrationManager.isStateInSyncWithServer()); + * return true; // reschedule + * } + * }; + * initializeTask = new RecurringTask("Token", scheduler, logger, smearer, expDelayGen, NO_DELAY, + * networkTimeoutMs) { + * @Override + * public boolean runTask() { + * // If token is still not assigned (as expected), sends a request. Otherwise, ignore. + * if (clientToken == null) { + * // Allocate a nonce and send a message requesting a new token. + * setNonce(ByteString.copyFromUtf8(Long.toString(internalScheduler.getCurrentTimeMs()))); + * protocolHandler.sendInitializeMessage(applicationClientId, nonce, debugString); + * return true; // reschedule to check state, retry if necessary after timeout + * } else { + * return false; // don't reschedule + * } + * } + * }; + *</pre> + * + */ +public abstract class RecurringTask extends InternalBase + implements Marshallable<RecurringTaskState> { + + /** Name of the task (for debugging purposes mostly). */ + private final String name; + + /** A logger */ + private final Logger logger; + + /** Scheduler for the scheduling the task as needed. */ + private final Scheduler scheduler; + + /** + * The time after which the task is scheduled first. If no delayGenerator is specified, this is + * also the delay used for retries. + */ + private final int initialDelayMs; + + /** For a task that is retried, add this time to the delay. */ + private final int timeoutDelayMs; + + /** A smearer for spreading the delays. */ + private final Smearer smearer; + + /** A delay generator for exponential backoff. */ + private final TiclExponentialBackoffDelayGenerator delayGenerator; + + /** The runnable that is scheduled for the task. */ + private final NamedRunnable runnable; + + /** If the task has been currently scheduled. */ + private boolean isScheduled; + + /** + * Creates a recurring task with the given parameters. The specs of the parameters are given in + * the instance variables. + * <p> + * The created task is first scheduled with a smeared delay of {@code initialDelayMs}. If the + * {@code this.run()} returns true on its execution, the task is rescheduled after a + * {@code timeoutDelayMs} + smeared delay of {@code initialDelayMs} or {@code timeoutDelayMs} + + * {@code delayGenerator.getNextDelay()} depending on whether the {@code delayGenerator} is null + * or not. + */ + + public RecurringTask(String name, Scheduler scheduler, Logger logger, Smearer smearer, + TiclExponentialBackoffDelayGenerator delayGenerator, + final int initialDelayMs, final int timeoutDelayMs) { + this.delayGenerator = delayGenerator; + this.name = Preconditions.checkNotNull(name); + this.logger = Preconditions.checkNotNull(logger); + this.scheduler = Preconditions.checkNotNull(scheduler); + this.smearer = Preconditions.checkNotNull(smearer); + this.initialDelayMs = initialDelayMs; + this.isScheduled = false; + this.timeoutDelayMs = timeoutDelayMs; + + // Create a runnable that runs the task. If the task asks for a retry, reschedule it after + // at a timeout delay. Otherwise, resets the delayGenerator. + this.runnable = createRunnable(); + } + + /** + * Creates a recurring task from {@code marshalledState}. Other parameters are as in the + * constructor above. + */ + RecurringTask(String name, Scheduler scheduler, Logger logger, Smearer smearer, + TiclExponentialBackoffDelayGenerator delayGenerator, + RecurringTaskState marshalledState) { + this(name, scheduler, logger, smearer, delayGenerator, marshalledState.getInitialDelayMs(), + marshalledState.getTimeoutDelayMs()); + this.isScheduled = marshalledState.getScheduled(); + } + + private NamedRunnable createRunnable() { + return new NamedRunnable(name) { + @Override + public void run() { + Preconditions.checkState(scheduler.isRunningOnThread(), "Not on scheduler thread"); + isScheduled = false; + if (runTask()) { + // The task asked to be rescheduled, so reschedule it after a timeout has occured. + Preconditions.checkState((delayGenerator != null) || (initialDelayMs != 0), + "Spinning: No exp back off and initialdelay is zero"); + ensureScheduled(true, "Retry"); + } else if (delayGenerator != null) { + // The task asked not to be rescheduled. Treat it as having "succeeded" and reset the + // delay generator. + delayGenerator.reset(); + } + } + }; + } + + /** + * Run the task and return true if the task should be rescheduled after a timeout. If false is + * returned, the task is not scheduled again until {@code ensureScheduled} is called again. + */ + public abstract boolean runTask(); + + /** Returns the smearer used for randomizing delays. */ + Smearer getSmearer() { + return smearer; + } + + /** Returns the delay generator, if any. */ + ExponentialBackoffDelayGenerator getDelayGenerator() { + return delayGenerator; + } + + /** + * Ensures that the task is scheduled (with {@code debugReason} as the reason to be printed + * for debugging purposes). If the task has been scheduled, it is not scheduled again. + * <p> + * REQUIRES: Must be called from the scheduler thread. + */ + + public void ensureScheduled(String debugReason) { + ensureScheduled(false, debugReason); + } + + /** + * Ensures that the task is scheduled if it is already not scheduled. If already scheduled, this + * method is a no-op. + * + * @param isRetry If this is {@code false}, smears the {@code initialDelayMs} and uses that delay + * for scheduling. If {@code isRetry} is true, it determines the new delay to be + * {@code timeoutDelayMs} + {@ocde delayGenerator.getNextDelay()} if + * {@code delayGenerator} is non-null. If {@code delayGenerator} is null, schedules the + * task after a delay of {@code timeoutDelayMs} + smeared value of {@code initialDelayMs} + * <p> + * REQUIRES: Must be called from the scheduler thread. + */ + private void ensureScheduled(boolean isRetry, String debugReason) { + Preconditions.checkState(scheduler.isRunningOnThread()); + if (isScheduled) { + return; + } + final int delayMs; + + if (isRetry) { + // For a retried task, determine the delay to be timeout + extra delay (depending on whether + // a delay generator was provided or not). + if (delayGenerator != null) { + delayMs = timeoutDelayMs + delayGenerator.getNextDelay(); + } else { + delayMs = timeoutDelayMs + smearer.getSmearedDelay(initialDelayMs); + } + } else { + delayMs = smearer.getSmearedDelay(initialDelayMs); + } + + logger.fine("[%s] Scheduling %s with a delay %s, Now = %s", debugReason, name, delayMs, + scheduler.getCurrentTimeMs()); + scheduler.schedule(delayMs, runnable); + isScheduled = true; + } + + /** For use only in the Android scheduler. */ + public NamedRunnable getRunnable() { + return runnable; + } + + @Override + public RecurringTaskState marshal() { + ExponentialBackoffState backoffState = + (delayGenerator == null) ? null : delayGenerator.marshal(); + return RecurringTaskState.create(initialDelayMs, timeoutDelayMs, isScheduled, backoffState); + } + + @Override + public void toCompactString(TextBuilder builder) { + builder.append("<RecurringTask: name=").append(name) + .append(", initialDelayMs=").append(initialDelayMs) + .append(", timeoutDelayMs=").append(timeoutDelayMs) + .append(", isScheduled=").append(isScheduled) + .append(">"); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RegistrationManager.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RegistrationManager.java new file mode 100644 index 0000000..e985295 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RegistrationManager.java @@ -0,0 +1,284 @@ +/* + * 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; + +import com.google.ipc.invalidation.common.DigestFunction; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.ticl.Statistics.ClientErrorType; +import com.google.ipc.invalidation.ticl.TestableInvalidationClient.RegistrationManagerState; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP.OpType; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary; +import com.google.ipc.invalidation.ticl.proto.CommonProtos; +import com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.InternalBase; +import com.google.ipc.invalidation.util.Marshallable; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.ipc.invalidation.util.TypedUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * Object to track desired client registrations. This class belongs to caller (e.g., + * InvalidationClientImpl) and is not thread-safe - the caller has to use this class in a + * thread-safe manner. + * + */ +class RegistrationManager extends InternalBase implements Marshallable<RegistrationManagerStateP> { + + /** Prefix used to request all registrations. */ + static final byte[] EMPTY_PREFIX = new byte[]{}; + + /** The set of regisrations that the application has requested for. */ + private DigestStore<ObjectIdP> desiredRegistrations; + + /** Statistics objects to track number of sent messages, etc. */ + private final Statistics statistics; + + /** Latest known server registration state summary. */ + private RegistrationSummary lastKnownServerSummary; + + /** + * Map of object ids and operation types for which we have not yet issued any registration-status + * upcall to the listener. We need this so that we can synthesize success upcalls if registration + * sync, rather than a server message, communicates to us that we have a successful + * (un)registration. + * <p> + * This is a map from object id to type, rather than a set of {@code RegistrationP}, because + * a set of {@code RegistrationP} would assume that we always get a response for every operation + * we issue, which isn't necessarily true (i.e., the server might send back an unregistration + * status in response to a registration request). + */ + private final Map<ObjectIdP, Integer> pendingOperations = new HashMap<ObjectIdP, Integer>(); + + private final Logger logger; + + public RegistrationManager(Logger logger, Statistics statistics, DigestFunction digestFn, + RegistrationManagerStateP registrationManagerState) { + this.logger = logger; + this.statistics = statistics; + this.desiredRegistrations = new SimpleRegistrationStore(digestFn); + + if (registrationManagerState == null) { + // Initialize the server summary with a 0 size and the digest corresponding + // to it. Using defaultInstance would wrong since the server digest will + // not match unnecessarily and result in an info message being sent. + this.lastKnownServerSummary = getRegistrationSummary(); + } else { + this.lastKnownServerSummary = registrationManagerState.getNullableLastKnownServerSummary(); + if (this.lastKnownServerSummary == null) { + // If no server summary is set, use a default with size 0. + this.lastKnownServerSummary = getRegistrationSummary(); + } + desiredRegistrations.add(registrationManagerState.getRegistrations()); + for (RegistrationP regOp : registrationManagerState.getPendingOperations()) { + pendingOperations.put(regOp.getObjectId(), regOp.getOpType()); + } + } + } + + /** + * Returns a copy of the registration manager's state + * <p> + * Direct test code MUST not call this method on a random thread. It must be called on the + * InvalidationClientImpl's internal thread. + */ + + RegistrationManagerState getRegistrationManagerStateCopyForTest() { + List<ObjectIdP> registeredObjects = new ArrayList<ObjectIdP>(); + registeredObjects.addAll(desiredRegistrations.getElements(EMPTY_PREFIX, 0)); + return new RegistrationManagerState(getRegistrationSummary(), lastKnownServerSummary, + registeredObjects); + } + + /** + * Sets the digest store to be {@code digestStore} for testing purposes. + * <p> + * REQUIRES: This method is called before the Ticl has done any operations on this object. + */ + + void setDigestStoreForTest(DigestStore<ObjectIdP> digestStore) { + this.desiredRegistrations = digestStore; + this.lastKnownServerSummary = getRegistrationSummary(); + } + + /** Perform registration/unregistation for all objects in {@code objectIds}. */ + Collection<ObjectIdP> performOperations(Collection<ObjectIdP> objectIds, int regOpType) { + // Record that we have pending operations on the objects. + for (ObjectIdP objectId : objectIds) { + pendingOperations.put(objectId, regOpType); + } + // Update the digest appropriately. + if (regOpType == RegistrationP.OpType.REGISTER) { + return desiredRegistrations.add(objectIds); + } else { + return desiredRegistrations.remove(objectIds); + } + } + + /** + * Returns a registration subtree for registrations where the digest of the object id begins with + * the prefix {@code digestPrefix} of {@code prefixLen} bits. This method may also return objects + * whose digest prefix does not match {@code digestPrefix}. + */ + RegistrationSubtree getRegistrations(byte[] digestPrefix, int prefixLen) { + return RegistrationSubtree.create(desiredRegistrations.getElements(digestPrefix, prefixLen)); + } + + /** + * Handles registration operation statuses from the server. Returns a list of booleans, one per + * registration status, that indicates whether the registration operation was both successful and + * agreed with the desired client state (i.e., for each registration status, + * (status.optype == register) == desiredRegistrations.contains(status.objectid)). + * <p> + * REQUIRES: the caller subsequently make an informRegistrationStatus or informRegistrationFailure + * upcall on the listener for each registration in {@code registrationStatuses}. + */ + List<Boolean> handleRegistrationStatus(List<RegistrationStatus> registrationStatuses) { + // Local-processing result code for each element of registrationStatuses. + List<Boolean> localStatuses = new ArrayList<Boolean>(registrationStatuses.size()); + for (RegistrationStatus registrationStatus : registrationStatuses) { + ObjectIdP objectIdProto = registrationStatus.getRegistration().getObjectId(); + + // The object is no longer pending, since we have received a server status for it, so + // remove it from the pendingOperations map. (It may or may not have existed in the map, + // since we can receive spontaneous status messages from the server.) + TypedUtil.remove(pendingOperations, objectIdProto); + + // We start off with the local-processing set as success, then potentially fail. + boolean isSuccess = true; + + // if the server operation succeeded, then local processing fails on "incompatibility" as + // defined above. + if (CommonProtos.isSuccess(registrationStatus.getStatus())) { + boolean appWantsRegistration = desiredRegistrations.contains(objectIdProto); + boolean isOpRegistration = + registrationStatus.getRegistration().getOpType() == RegistrationP.OpType.REGISTER; + boolean discrepancyExists = isOpRegistration ^ appWantsRegistration; + if (discrepancyExists) { + // Remove the registration and set isSuccess to false, which will cause the caller to + // issue registration-failure to the application. + desiredRegistrations.remove(objectIdProto); + statistics.recordError(ClientErrorType.REGISTRATION_DISCREPANCY); + logger.info("Ticl discrepancy detected: registered = %s, requested = %s. " + + "Removing %s from requested", + isOpRegistration, appWantsRegistration, objectIdProto); + isSuccess = false; + } + } else { + // If the server operation failed, then also local processing fails. + desiredRegistrations.remove(objectIdProto); + logger.fine("Removing %s from committed", objectIdProto); + isSuccess = false; + } + localStatuses.add(isSuccess); + } + return localStatuses; + } + + /** + * Removes all desired registrations and pending operations. Returns all object ids + * that were affected. + * <p> + * REQUIRES: the caller issue a permanent failure upcall to the listener for all returned object + * ids. + */ + Collection<ObjectIdP> removeRegisteredObjects() { + int numObjects = desiredRegistrations.size() + pendingOperations.size(); + Set<ObjectIdP> failureCalls = new HashSet<ObjectIdP>(numObjects); + failureCalls.addAll(desiredRegistrations.removeAll()); + failureCalls.addAll(pendingOperations.keySet()); + pendingOperations.clear(); + return failureCalls; + } + + // + // Digest-related methods + // + + /** Returns a summary of the desired registrations. */ + RegistrationSummary getRegistrationSummary() { + return RegistrationSummary.create(desiredRegistrations.size(), + new Bytes(desiredRegistrations.getDigest())); + } + + /** + * Informs the manager of a new registration state summary from the server. + * Returns a possibly-empty map of <object-id, reg-op-type>. For each entry in the map, + * the caller should make an inform-registration-status upcall on the listener. + */ + Set<RegistrationP> informServerRegistrationSummary( + RegistrationSummary regSummary) { + if (regSummary != null) { + this.lastKnownServerSummary = regSummary; + } + if (isStateInSyncWithServer()) { + // If we are now in sync with the server, then the caller should make inform-reg-status + // upcalls for all operations that we had pending, if any; they are also no longer pending. + Set<RegistrationP> upcallsToMake = new HashSet<RegistrationP>(pendingOperations.size()); + for (Map.Entry<ObjectIdP, Integer> entry : pendingOperations.entrySet()) { + ObjectIdP objectId = entry.getKey(); + boolean isReg = entry.getValue() == OpType.REGISTER; + upcallsToMake.add(CommonProtos.newRegistrationP(objectId, isReg)); + } + pendingOperations.clear(); + return upcallsToMake; + } else { + // If we are not in sync with the server, then the caller should make no upcalls. + return Collections.emptySet(); + } + } + + /** + * Returns whether the local registration state and server state agree, based on the last + * received server summary (from {@link #informServerRegistrationSummary}). + */ + boolean isStateInSyncWithServer() { + return TypedUtil.<RegistrationSummary>equals(lastKnownServerSummary, getRegistrationSummary()); + } + + @Override + public void toCompactString(TextBuilder builder) { + builder.appendFormat("Last known digest: %s, Requested regs: %s", lastKnownServerSummary, + desiredRegistrations); + } + + @Override + public RegistrationManagerStateP marshal() { + List<ObjectIdP> desiredRegistrations = + new ArrayList<ObjectIdP>(this.desiredRegistrations.getElements(EMPTY_PREFIX, 0)); + List<RegistrationP> pendingOperations = + new ArrayList<RegistrationP>(this.pendingOperations.size()); + for (Map.Entry<ObjectIdP, Integer> entry : this.pendingOperations.entrySet()) { + pendingOperations.add(RegistrationP.create(entry.getKey(), entry.getValue())); + } + return RegistrationManagerStateP.create(desiredRegistrations, lastKnownServerSummary, + pendingOperations); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RunState.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RunState.java new file mode 100644 index 0000000..6e022b0 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RunState.java @@ -0,0 +1,95 @@ +/* + * 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; + +import com.google.ipc.invalidation.ticl.proto.Client.RunStateP; +import com.google.ipc.invalidation.util.Marshallable; + +/** + * An abstraction that keeps track of whether the caller is started or stopped and only allows + * the following transitions NOT_STARTED -> STARTED -> STOPPED. This class is thread-safe. + * + */ +public class RunState implements Marshallable<RunStateP> { + /** Current run state ({@link RunStateP}). */ + private Integer currentState; + private Object lock = new Object(); + + /** Constructs a new instance in the {@code NOT_STARTED} state. */ + public RunState() { + currentState = RunStateP.State.NOT_STARTED; + } + + /** Constructs a new instance with the state given in {@code runState}. */ + RunState(RunStateP runState) { + this.currentState = runState.getState(); + } + + /** + * Marks the current state to be STARTED. + * <p> + * REQUIRES: Current state is NOT_STARTED. + */ + public void start() { + synchronized (lock) { + if (currentState != RunStateP.State.NOT_STARTED) { + throw new IllegalStateException("Cannot start: " + currentState); + } + currentState = RunStateP.State.STARTED; + } + } + + /** + * Marks the current state to be STOPPED. + * <p> + * REQUIRES: Current state is STARTED. + */ + public void stop() { + synchronized (lock) { + if (currentState != RunStateP.State.STARTED) { + throw new IllegalStateException("Cannot stop: " + currentState); + } + currentState = RunStateP.State.STOPPED; + } + } + + /** + * Returns true iff {@link #start} has been called on this but {@link #stop} has not been called. + */ + public boolean isStarted() { + synchronized (lock) { + return currentState == RunStateP.State.STARTED; + } + } + + /** Returns true iff {@link #start} and {@link #stop} have been called on this object. */ + public boolean isStopped() { + synchronized (lock) { + return currentState == RunStateP.State.STOPPED; + } + } + + @Override + public RunStateP marshal() { + return RunStateP.create(currentState); + } + + @Override + public String toString() { + return "<RunState: " + currentState + ">"; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/SafeStorage.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/SafeStorage.java new file mode 100644 index 0000000..55d754a --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/SafeStorage.java @@ -0,0 +1,111 @@ +/* + * 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; + +import static com.google.ipc.invalidation.external.client.SystemResources.Scheduler.NO_DELAY; + +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.external.client.SystemResources.Storage; +import com.google.ipc.invalidation.external.client.types.Callback; +import com.google.ipc.invalidation.external.client.types.SimplePair; +import com.google.ipc.invalidation.external.client.types.Status; +import com.google.ipc.invalidation.util.NamedRunnable; +import com.google.ipc.invalidation.util.Preconditions; + +/** + * An implementation of the Storage resource that schedules the callbacks on the given scheduler + * thread. + * + */ +public class SafeStorage implements Storage { + + /** The delegate to which the calls are forwarded. */ + private final Storage delegate; + + /** The scheduler on which the callbacks are scheduled. */ + private Scheduler scheduler; + + SafeStorage(Storage delegate) { + this.delegate = Preconditions.checkNotNull(delegate); + } + + @Override + public void setSystemResources(SystemResources resources) { + this.scheduler = resources.getInternalScheduler(); + } + + @Override + public void writeKey(String key, byte[] value, final Callback<Status> done) { + delegate.writeKey(key, value, new Callback<Status>() { + @Override + public void accept(final Status status) { + scheduler.schedule(NO_DELAY, new NamedRunnable("SafeStorage.writeKey") { + @Override + public void run() { + done.accept(status); + } + }); + } + }); + } + + @Override + public void readKey(String key, final Callback<SimplePair<Status, byte[]>> done) { + delegate.readKey(key, new Callback<SimplePair<Status, byte[]>>() { + @Override + public void accept(final SimplePair<Status, byte[]> result) { + scheduler.schedule(NO_DELAY, new NamedRunnable("SafeStorage.readKey") { + @Override + public void run() { + done.accept(result); + } + }); + } + }); + } + + @Override + public void deleteKey(String key, final Callback<Boolean> done) { + delegate.deleteKey(key, new Callback<Boolean>() { + @Override + public void accept(final Boolean success) { + scheduler.schedule(NO_DELAY, new NamedRunnable("SafeStorage.deleteKey") { + @Override + public void run() { + done.accept(success); + } + }); + } + }); + } + + @Override + public void readAllKeys(final Callback<SimplePair<Status, String>> keyCallback) { + delegate.readAllKeys(new Callback<SimplePair<Status, String>>() { + @Override + public void accept(final SimplePair<Status, String> keyResult) { + scheduler.schedule(NO_DELAY, new NamedRunnable("SafeStorage.readAllKeys") { + @Override + public void run() { + keyCallback.accept(keyResult); + } + }); + } + }); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/SimpleRegistrationStore.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/SimpleRegistrationStore.java new file mode 100644 index 0000000..3ecbef9 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/SimpleRegistrationStore.java @@ -0,0 +1,148 @@ +/* + * 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; + +import com.google.ipc.invalidation.common.DigestFunction; +import com.google.ipc.invalidation.common.ObjectIdDigestUtils; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.InternalBase; +import com.google.ipc.invalidation.util.TextBuilder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Simple, map-based implementation of {@link DigestStore}. + * + */ +class SimpleRegistrationStore extends InternalBase implements DigestStore<ObjectIdP> { + + /** All the registrations in the store mapped from the digest to the Object Id. */ + private final SortedMap<Bytes, ObjectIdP> registrations = new TreeMap<Bytes, ObjectIdP>(); + + /** The function used to compute digests of objects. */ + private final DigestFunction digestFunction; + + /** The memoized digest of all objects in registrations. */ + private Bytes digest; + + SimpleRegistrationStore(DigestFunction digestFunction) { + this.digestFunction = digestFunction; + recomputeDigest(); + } + + @Override + public boolean add(ObjectIdP oid) { + if (registrations.put(ObjectIdDigestUtils.getDigest(oid.getSource(), + oid.getName().getByteArray(), digestFunction), oid) == null) { + recomputeDigest(); + return true; + } + return false; + } + + @Override + public Collection<ObjectIdP> add(Collection<ObjectIdP> oids) { + Collection<ObjectIdP> addedOids = new ArrayList<ObjectIdP>(); + for (ObjectIdP oid : oids) { + if (registrations.put(ObjectIdDigestUtils.getDigest(oid.getSource(), + oid.getName().getByteArray(), digestFunction), oid) == null) { + // There was no previous value, so this is a new item. + addedOids.add(oid); + } + } + if (!addedOids.isEmpty()) { + // Only recompute the digest if we made changes. + recomputeDigest(); + } + return addedOids; + } + + @Override + public boolean remove(ObjectIdP oid) { + if (registrations.remove(ObjectIdDigestUtils.getDigest(oid.getSource(), + oid.getName().getByteArray(), digestFunction)) != null) { + recomputeDigest(); + return true; + } + return false; + } + + @Override + public Collection<ObjectIdP> remove(Collection<ObjectIdP> oids) { + Collection<ObjectIdP> removedOids = new ArrayList<ObjectIdP>(); + for (ObjectIdP oid : oids) { + if (registrations.remove(ObjectIdDigestUtils.getDigest(oid.getSource(), + oid.getName().getByteArray(), digestFunction)) != null) { + removedOids.add(oid); + } + } + if (!removedOids.isEmpty()) { + // Only recompute the digest if we made changes. + recomputeDigest(); + } + return removedOids; + } + + @Override + public Collection<ObjectIdP> removeAll() { + Collection<ObjectIdP> result = new ArrayList<ObjectIdP>(registrations.values()); + registrations.clear(); + recomputeDigest(); + return result; + } + + @Override + public boolean contains(ObjectIdP oid) { + return registrations.containsKey(ObjectIdDigestUtils.getDigest(oid.getSource(), + oid.getName().getByteArray(), digestFunction)); + } + + @Override + public int size() { + return registrations.size(); + } + + @Override + public byte[] getDigest() { + return digest.getByteArray(); + } + + @Override + public Collection<ObjectIdP> getElements(byte[] oidDigestPrefix, int prefixLen) { + // We always return all the registrations and let the Ticl sort it out. + return registrations.values(); + } + + /** Recomputes the digests over all objects and sets {@code this.digest}. */ + private void recomputeDigest() { + this.digest = ObjectIdDigestUtils.getDigest(registrations.keySet(), digestFunction); + } + + @Override + public void toCompactString(TextBuilder builder) { + builder + .append("<SimpleRegistrationStore: registrations=") + .append(registrations.values()) + .append(", digest=") + .append(digest) + .append(">"); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/Statistics.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/Statistics.java new file mode 100644 index 0000000..899955e --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/Statistics.java @@ -0,0 +1,353 @@ +/* + * 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; + +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.types.SimplePair; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord; +import com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState; +import com.google.ipc.invalidation.util.InternalBase; +import com.google.ipc.invalidation.util.Marshallable; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.ipc.invalidation.util.TypedUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Statistics for the Ticl, e.g., number of registration calls, number of token mismatches, etc. + * + */ +public class Statistics extends InternalBase implements Marshallable<StatisticsState> { + + // Implementation: To classify the statistics a bit better, we have a few enums to track different + // types of statistics, e.g., sent message types, errors, etc. For each statistic type, we create + // a map and provide a method to record an event for each type of statistic. + + /** Types of messages sent to the server: {@code ClientToServerMessage} for their description. */ + public enum SentMessageType { + INFO, + INITIALIZE, + INVALIDATION_ACK, + REGISTRATION, + REGISTRATION_SYNC, + TOTAL, // Refers to the actual ClientToServerMessage message sent on the network. + } + + /** + * Types of messages received from the server: {@code ServerToClientMessage} for their + * description. + */ + public enum ReceivedMessageType { + INFO_REQUEST, + INVALIDATION, + REGISTRATION_STATUS, + REGISTRATION_SYNC_REQUEST, + TOKEN_CONTROL, + ERROR, + CONFIG_CHANGE, + STALE_INVALIDATION, // An already acked INVALIDATION. + TOTAL, // Refers to the actual ServerToClientMessage messages received from the network. + } + + /** Interesting API calls coming from the application ({@code InvalidationClient}). */ + public enum IncomingOperationType { + ACKNOWLEDGE, + REGISTRATION, + UNREGISTRATION, + } + + /** Different types of events issued by the {@code InvalidationListener}). */ + public enum ListenerEventType { + INFORM_ERROR, + INFORM_REGISTRATION_FAILURE, + INFORM_REGISTRATION_STATUS, + INVALIDATE, + INVALIDATE_ALL, + INVALIDATE_UNKNOWN, + REISSUE_REGISTRATIONS, + } + + /** Different types of errors observed by the Ticl. */ + public enum ClientErrorType { + /** Acknowledge call received from client with a bad handle. */ + ACKNOWLEDGE_HANDLE_FAILURE, + + /** Incoming message dropped due to parsing, validation problems. */ + INCOMING_MESSAGE_FAILURE, + + /** Tried to send an outgoing message that was invalid. */ + OUTGOING_MESSAGE_FAILURE, + + /** Persistent state failed to deserialize correctly. */ + PERSISTENT_DESERIALIZATION_FAILURE, + + /** Read of blob from persistent state failed. */ + PERSISTENT_READ_FAILURE, + + /** Write of blob from persistent state failed. */ + PERSISTENT_WRITE_FAILURE, + + /** Message received with incompatible protocol version. */ + PROTOCOL_VERSION_FAILURE, + + /** + * Registration at client and server is different, e.g., client thinks it is registered while + * the server says it is unregistered (of course, sync will fix it). + */ + REGISTRATION_DISCREPANCY, + + /** The nonce from the server did not match the current nonce by the client. */ + NONCE_MISMATCH, + + /** The current token at the client is different from the token in the incoming message. */ + TOKEN_MISMATCH, + + /** No message sent due to token missing. */ + TOKEN_MISSING_FAILURE, + + /** Received a message with a token (transient) failure. */ + TOKEN_TRANSIENT_FAILURE, + } + + // Names of statistics types. Do not rely on reflection to determine type names because Proguard + // may change them for Android clients. + private static final String SENT_MESSAGE_TYPE_NAME = "SentMessageType"; + private static final String INCOMING_OPERATION_TYPE_NAME = "IncomingOperationType"; + private static final String RECEIVED_MESSAGE_TYPE_NAME = "ReceivedMessageType"; + private static final String LISTENER_EVENT_TYPE_NAME = "ListenerEventType"; + private static final String CLIENT_ERROR_TYPE_NAME = "ClientErrorType"; + + // Map from stats enum names to values. Used in place of Enum.valueOf() because this method + // invokes Enum.values() via reflection, and that method may be renamed by Proguard. + private static final Map<String, SentMessageType> SENT_MESSAGE_TYPE_NAME_TO_VALUE_MAP = + createValueOfMap(SentMessageType.values()); + private static final Map<String, IncomingOperationType> + INCOMING_OPERATION_TYPE_NAME_TO_VALUE_MAP = createValueOfMap(IncomingOperationType.values()); + private static final Map<String, ReceivedMessageType> RECEIVED_MESSAGE_TYPE_NAME_TO_VALUE_MAP = + createValueOfMap(ReceivedMessageType.values()); + private static final Map<String, ListenerEventType> LISTENER_EVENT_TYPE_NAME_TO_VALUE_MAP = + createValueOfMap(ListenerEventType.values()); + private static final Map<String, ClientErrorType> CLIENT_ERROR_TYPE_NAME_TO_VALUE_MAP = + createValueOfMap(ClientErrorType.values()); + + // Maps for each type of Statistic to keep track of how many times each event has occurred. + + private final Map<SentMessageType, Integer> sentMessageTypes = + new HashMap<SentMessageType, Integer>(); + private final Map<ReceivedMessageType, Integer> receivedMessageTypes = + new HashMap<ReceivedMessageType, Integer>(); + private final Map<IncomingOperationType, Integer> incomingOperationTypes = + new HashMap<IncomingOperationType, Integer>(); + private final Map<ListenerEventType, Integer> listenerEventTypes = + new HashMap<ListenerEventType, Integer>(); + private final Map<ClientErrorType, Integer> clientErrorTypes = + new HashMap<ClientErrorType, Integer>(); + + public Statistics() { + initializeMap(sentMessageTypes, SentMessageType.values()); + initializeMap(receivedMessageTypes, ReceivedMessageType.values()); + initializeMap(incomingOperationTypes, IncomingOperationType.values()); + initializeMap(listenerEventTypes, ListenerEventType.values()); + initializeMap(clientErrorTypes, ClientErrorType.values()); + } + + /** Returns a copy of this. */ + public Statistics getCopyForTest() { + Statistics statistics = new Statistics(); + statistics.sentMessageTypes.putAll(sentMessageTypes); + statistics.receivedMessageTypes.putAll(receivedMessageTypes); + statistics.incomingOperationTypes.putAll(incomingOperationTypes); + statistics.listenerEventTypes.putAll(listenerEventTypes); + statistics.clientErrorTypes.putAll(clientErrorTypes); + return statistics; + } + + /** Returns the counter value for {@code clientErrorType}. */ + int getClientErrorCounterForTest(ClientErrorType clientErrorType) { + return TypedUtil.mapGet(clientErrorTypes, clientErrorType); + } + + /** Returns the counter value for {@code sentMessageType}. */ + int getSentMessageCounterForTest(SentMessageType sentMessageType) { + return TypedUtil.mapGet(sentMessageTypes, sentMessageType); + } + + /** Returns the counter value for {@code receivedMessageType}. */ + int getReceivedMessageCounterForTest(ReceivedMessageType receivedMessageType) { + return TypedUtil.mapGet(receivedMessageTypes, receivedMessageType); + } + + /** Records the fact that a message of type {@code sentMessageType} has been sent. */ + public void recordSentMessage(SentMessageType sentMessageType) { + incrementValue(sentMessageTypes, sentMessageType); + } + + /** Records the fact that a message of type {@code receivedMessageType} has been received. */ + public void recordReceivedMessage(ReceivedMessageType receivedMessageType) { + incrementValue(receivedMessageTypes, receivedMessageType); + } + + /** + * Records the fact that the application has made a call of type + * {@code incomingOperationType}. + */ + public void recordIncomingOperation(IncomingOperationType incomingOperationType) { + incrementValue(incomingOperationTypes, incomingOperationType); + } + + /** Records the fact that the listener has issued an event of type {@code listenerEventType}. */ + public void recordListenerEvent(ListenerEventType listenerEventType) { + incrementValue(listenerEventTypes, listenerEventType); + } + + /** Records the fact that the client has observed an error of type {@code clientErrorType}. */ + public void recordError(ClientErrorType clientErrorType) { + incrementValue(clientErrorTypes, clientErrorType); + } + + /** + * Modifies {@code performanceCounters} to contain all the statistics that are non-zero. Each pair + * has the name of the statistic event and the number of times that event has occurred since the + * client started. + */ + public void getNonZeroStatistics(List<SimplePair<String, Integer>> performanceCounters) { + // Add the non-zero values from the different maps to performanceCounters. + fillWithNonZeroStatistics(sentMessageTypes, performanceCounters, SENT_MESSAGE_TYPE_NAME); + fillWithNonZeroStatistics(receivedMessageTypes, performanceCounters, + RECEIVED_MESSAGE_TYPE_NAME); + fillWithNonZeroStatistics(incomingOperationTypes, performanceCounters, + INCOMING_OPERATION_TYPE_NAME); + fillWithNonZeroStatistics(listenerEventTypes, performanceCounters, LISTENER_EVENT_TYPE_NAME); + fillWithNonZeroStatistics(clientErrorTypes, performanceCounters, CLIENT_ERROR_TYPE_NAME); + } + + /** Modifies {@code result} to contain those statistics from {@code map} whose value is > 0. */ + private static <Key extends Enum<Key>> void fillWithNonZeroStatistics(Map<Key, Integer> map, + List<SimplePair<String, Integer>> destination, String typeName) { + String prefix = typeName + "."; + for (Map.Entry<Key, Integer> entry : map.entrySet()) { + if (entry.getValue() > 0) { + destination.add(SimplePair.of(prefix + entry.getKey().name(), entry.getValue())); + } + } + } + + /** Initializes a map from enum names to values of the given {@code keys}. */ + private static <Key extends Enum<Key>> Map<String, Key> createValueOfMap(Key[] keys) { + HashMap<String, Key> map = new HashMap<String, Key>(); + for (Key key : keys) { + map.put(key.name(), key); + } + return map; + } + + /** Increments the value of {@code map}[{@code key}] by 1. */ + private static <Key> void incrementValue(Map<Key, Integer> map, Key key) { + map.put(key, TypedUtil.mapGet(map, key) + 1); + } + + /** Initializes all values for {@code keys} in {@code map} to be 0. */ + private static <Key> void initializeMap(Map<Key, Integer> map, Key[] keys) { + for (Key key : keys) { + map.put(key, 0); + } + } + + @Override + public void toCompactString(TextBuilder builder) { + List<SimplePair<String, Integer>> nonZeroValues = new ArrayList<SimplePair<String, Integer>>(); + getNonZeroStatistics(nonZeroValues); + builder.appendFormat("Client Statistics: %s\n", nonZeroValues); + } + + @Override + public StatisticsState marshal() { + // Get all the non-zero counters, convert them to proto PropertyRecord messages, and return + // a StatisticsState containing the records. + List<SimplePair<String, Integer>> counters = new ArrayList<SimplePair<String, Integer>>(); + getNonZeroStatistics(counters); + List<PropertyRecord> propertyRecords = new ArrayList<PropertyRecord>(counters.size()); + for (SimplePair<String, Integer> counter : counters) { + propertyRecords.add(PropertyRecord.create(counter.getFirst(), counter.getSecond())); + } + return StatisticsState.create(propertyRecords); + } + + /** + * Given the serialized {@code performanceCounters} of the client statistics, returns a Statistics + * object with the performance counter values from {@code performanceCounters}. + */ + + public static Statistics deserializeStatistics(Logger logger, + Collection<PropertyRecord> performanceCounters) { + Statistics statistics = new Statistics(); + + // For each counter, parse out the counter name and value. + for (PropertyRecord performanceCounter : performanceCounters) { + String counterName = performanceCounter.getName(); + String[] parts = counterName.split("\\."); + if (parts.length != 2) { + logger.warning("Perf counter name must of form: class.value, skipping: %s", counterName); + continue; + } + String className = parts[0]; + String fieldName = parts[1]; + int counterValue = performanceCounter.getValue(); + + // Call the relevant method in a loop (i.e., depending on the type of the class). + if (TypedUtil.<String>equals(className, SENT_MESSAGE_TYPE_NAME)) { + incrementPerformanceCounterValue(logger, SENT_MESSAGE_TYPE_NAME_TO_VALUE_MAP, + statistics.sentMessageTypes, fieldName, counterValue); + } else if (TypedUtil.<String>equals(className, INCOMING_OPERATION_TYPE_NAME)) { + incrementPerformanceCounterValue(logger, INCOMING_OPERATION_TYPE_NAME_TO_VALUE_MAP, + statistics.incomingOperationTypes, fieldName, counterValue); + } else if (TypedUtil.<String>equals(className, RECEIVED_MESSAGE_TYPE_NAME)) { + incrementPerformanceCounterValue(logger, RECEIVED_MESSAGE_TYPE_NAME_TO_VALUE_MAP, + statistics.receivedMessageTypes, fieldName, counterValue); + } else if (TypedUtil.<String>equals(className, LISTENER_EVENT_TYPE_NAME)) { + incrementPerformanceCounterValue(logger, LISTENER_EVENT_TYPE_NAME_TO_VALUE_MAP, + statistics.listenerEventTypes, fieldName, counterValue); + } else if (TypedUtil.<String>equals(className, CLIENT_ERROR_TYPE_NAME)) { + incrementPerformanceCounterValue(logger, CLIENT_ERROR_TYPE_NAME_TO_VALUE_MAP, + statistics.clientErrorTypes, fieldName, counterValue); + } else { + logger.warning("Skipping unknown enum class name %s", className); + } + } + return statistics; + } + + /** + * Looks for an enum value with the given {@code fieldName} in {@code valueOfMap} and increments + * the corresponding entry in {@code counts} by {@code counterValue}. Call to update statistics + * for a single performance counter. + */ + private static <Key extends Enum<Key>> void incrementPerformanceCounterValue(Logger logger, + Map<String, Key> valueOfMap, Map<Key, Integer> counts, String fieldName, int counterValue) { + Key type = TypedUtil.mapGet(valueOfMap, fieldName); + if (type != null) { + int currentValue = TypedUtil.mapGet(counts, type); + counts.put(type, currentValue + counterValue); + } else { + logger.warning("Skipping unknown enum value name %s", fieldName); + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TestableInvalidationClient.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TestableInvalidationClient.java new file mode 100644 index 0000000..898c10b --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TestableInvalidationClient.java @@ -0,0 +1,158 @@ +/* + * 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; + +import com.google.ipc.invalidation.common.DigestFunction; +import com.google.ipc.invalidation.external.client.InvalidationClient; +import com.google.ipc.invalidation.external.client.InvalidationListener; +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.ticl.proto.ChannelCommon.NetworkEndpointId; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.InternalBase; +import com.google.ipc.invalidation.util.Preconditions; +import com.google.ipc.invalidation.util.TextBuilder; + +import java.util.ArrayList; +import java.util.Collection; + + +/** + * An interface that exposes some extra methods for testing an invalidation client implementation. + * + */ +public interface TestableInvalidationClient extends InvalidationClient { + + /** The state of the registration manager exposed for testing. */ + public class RegistrationManagerState extends InternalBase { + + /** The registration summary of all objects registered by the client (known at the client). */ + private final RegistrationSummary clientSummary; + + /** The last known registration summary from the server. */ + private final RegistrationSummary serverSummary; + + /** The objects registered by the client (as known at the client). */ + private final Collection<ObjectIdP> registeredObjects; + + public RegistrationManagerState(RegistrationSummary clientSummary, + RegistrationSummary serverSummary, ObjectIdP[] registeredObjects) { + this(clientSummary, serverSummary, new ArrayList<ObjectIdP>(registeredObjects.length)); + for (ObjectIdP registeredObject : registeredObjects) { + this.registeredObjects.add(registeredObject); + } + } + + public RegistrationManagerState(RegistrationSummary clientSummary, + RegistrationSummary serverSummary, Collection<ObjectIdP> registeredObjects) { + this.clientSummary = Preconditions.checkNotNull(clientSummary); + this.serverSummary = Preconditions.checkNotNull(serverSummary); + this.registeredObjects = Preconditions.checkNotNull(registeredObjects); + } + + public RegistrationSummary getClientSummary() { + return clientSummary; + } + + public RegistrationSummary getServerSummary() { + return serverSummary; + } + + public Collection<ObjectIdP> getRegisteredObjects() { + return registeredObjects; + } + + @Override + public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationManagerState: clientSummary=").append(clientSummary); + builder.append(", serverSummary=").append(serverSummary); + builder.append(", registeredObjects=<").append(registeredObjects).append(">"); + } + } + + /** Returns whether the Ticl is started. */ + boolean isStartedForTest(); + + /** Stops the system resources. */ + void stopResources(); + + /** Returns the current time on the client. */ + long getResourcesTimeMs(); + + /** Returns the client internal scheduler */ + SystemResources.Scheduler getInternalSchedulerForTest(); + + /** Returns the client storage. */ + SystemResources.Storage getStorage(); + + /** Returns a snapshot of the performance counters/statistics . */ + Statistics getStatisticsForTest(); + + /** Returns the digest function used for computing digests for object registrations. */ + DigestFunction getDigestFunctionForTest(); + + /** + * Returns a copy of the registration manager's state + * <p> + * REQUIRES: This method is called on the internal scheduler. + */ + RegistrationManagerState getRegistrationManagerStateCopyForTest(); + + /** + * Changes the existing delay for the network timeout delay in the operation scheduler to be + * {@code delayMs}. + */ + void changeNetworkTimeoutDelayForTest(int delayMs); + + /** + * Changes the existing delay for the heartbeat delay in the operation scheduler to be + * {@code delayMs}. + */ + void changeHeartbeatDelayForTest(int delayMs); + + /** + * Sets the digest store to be {@code digestStore} for testing purposes. + * <p> + * REQUIRES: This method is called before the Ticl has been started. + */ + void setDigestStoreForTest(DigestStore<ObjectIdP> digestStore); + + /** Returns the client id that is used for squelching invalidations on the server side. */ + byte[] getApplicationClientIdForTest(); + + /** Returns the listener that was registered by the caller. */ + InvalidationListener getInvalidationListenerForTest(); + + /** Returns the current client token. */ + Bytes getClientTokenForTest(); + + /** Returns the single key used to write all the Ticl state. */ + String getClientTokenKeyForTest(); + + /** Returns the next time a message is allowed to be sent to the server (could be in the past). */ + long getNextMessageSendTimeMsForTest(); + + /** Returns the configuration used by the client. */ + ClientConfigP getConfigForTest(); + + /** + * Returns the network endpoint id of the client. May throw {@code UnsupportedOperationException}. + */ + NetworkEndpointId getNetworkIdForTest(); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TestableNetworkChannel.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TestableNetworkChannel.java new file mode 100644 index 0000000..79cc662 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TestableNetworkChannel.java @@ -0,0 +1,33 @@ +/* + * 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; + +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.ticl.proto.ChannelCommon.NetworkEndpointId; + +/** + * Extension of {@link com.google.ipc.invalidation.external.client.SystemResources.NetworkChannel} + * that adds a method to get the network endpoint id. + * + */ +public interface TestableNetworkChannel extends SystemResources.NetworkChannel { + /** + * Returns the network id for testing. May throw {@link UnsupportedOperationException}. + */ + NetworkEndpointId getNetworkIdForTest(); + +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TiclExponentialBackoffDelayGenerator.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TiclExponentialBackoffDelayGenerator.java new file mode 100644 index 0000000..fd6d826 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TiclExponentialBackoffDelayGenerator.java @@ -0,0 +1,57 @@ +/* + * 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; + +import com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState; +import com.google.ipc.invalidation.util.ExponentialBackoffDelayGenerator; +import com.google.ipc.invalidation.util.Marshallable; + +import java.util.Random; + +/** + * A subclass of {@link ExponentialBackoffDelayGenerator} that supports (un)marshalling to and from + * protocol buffers. + * + */ +public class TiclExponentialBackoffDelayGenerator + extends ExponentialBackoffDelayGenerator implements Marshallable<ExponentialBackoffState> { + + /** + * Creates an exponential backoff delay generator. Parameters are as in + * {@link ExponentialBackoffDelayGenerator#ExponentialBackoffDelayGenerator(Random, int, int)}. + */ + public TiclExponentialBackoffDelayGenerator(Random random, int initialMaxDelay, + int maxExponentialFactor) { + super(random, initialMaxDelay, maxExponentialFactor); + } + + /** + * Restores a generator from {@code marshalledState}. Other parameters are as in + * {@link ExponentialBackoffDelayGenerator#ExponentialBackoffDelayGenerator(Random, int, int)}. + * + * @param marshalledState marshalled state from which to restore. + */ + public TiclExponentialBackoffDelayGenerator(Random random, int initialMaxDelay, + int maxExponentialFactor, ExponentialBackoffState marshalledState) { + super(random, initialMaxDelay, maxExponentialFactor, marshalledState.getCurrentMaxDelay(), + marshalledState.getInRetryMode()); + } + + @Override + public ExponentialBackoffState marshal() { + return ExponentialBackoffState.create(getCurrentMaxDelay(), getInRetryMode()); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidClock.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidClock.java new file mode 100644 index 0000000..84bd15f --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidClock.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * Interface for the Android Ticl that provides a source of time. + * + */ +public interface AndroidClock { + /** + * Implementation of {@code AndroidClock} that uses {@link System#currentTimeMillis()}. + */ + static class SystemClock implements AndroidClock { + @Override + public long nowMs() { + return System.currentTimeMillis(); + } + } + + /** Returns milliseconds elapsed since the Unix epoch. */ + long nowMs(); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInternalScheduler.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInternalScheduler.java new file mode 100644 index 0000000..f867d6c --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInternalScheduler.java @@ -0,0 +1,208 @@ +/* + * 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.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.ticl.RecurringTask; +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidSchedulerEvent; +import com.google.ipc.invalidation.util.NamedRunnable; +import com.google.ipc.invalidation.util.Preconditions; +import com.google.ipc.invalidation.util.TypedUtil; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Scheduler for controlling access to internal Ticl state in Android. + * <p> + * This class maintains a map from recurring task names to the recurring task instances in the + * associated Ticl. To schedule a recurring task, it uses the {@link AlarmManager} to schedule + * an intent to itself at the appropriate time in the future. This intent contains the name of + * the task to run; when it is received, this class looks up the appropriate recurring task + * instance and runs it. + * <p> + * Note that this class only supports scheduling recurring tasks, not ordinary runnables. In + * order for it to be used, the application must declare the AlarmReceiver of the scheduler + * in the application's manifest file; see the implementation comment in AlarmReceiver for + * details. + * + */ +public final class AndroidInternalScheduler implements Scheduler { + /** Class that receives AlarmManager broadcasts and reissues them as intents for this service. */ + public static final class AlarmReceiver extends BroadcastReceiver { + /* + * This class needs to be public so that it can be instantiated by the Android runtime. + * Additionally, it should be declared as a broadcast receiver in the application manifest: + * <receiver android:name="com.google.ipc.invalidation.ticl.android2.\ + * AndroidInternalScheduler$AlarmReceiver" android:enabled="true"/> + */ + + @Override + public void onReceive(Context context, Intent intent) { + // Resend the intent to the service so that it's processed on the handler thread and with + // the automatic shutdown logic provided by IntentService. + intent.setClassName(context, new AndroidTiclManifest(context).getTiclServiceClass()); + context.startService(intent); + } + } + + /** + * If {@code true}, {@link #isRunningOnThread} will verify that calls are being made from either + * the {@link TiclService} or the {@link TestableTiclService.TestableClient}. + */ + public static boolean checkStackForTest = false; + + /** Class name of the testable client class, for checking call stacks in tests. */ + private static final String TESTABLE_CLIENT_CLASSNAME_FOR_TEST = + "com.google.ipc.invalidation.ticl.android2.TestableTiclService$TestableClient"; + + /** + * {@link RecurringTask}-created runnables that can be executed by this instance, by their names. + */ + private final Map<String, Runnable> registeredTasks = new HashMap<String, Runnable>(); + + /** Android system context. */ + private final Context context; + + /** Source of time for computing scheduling delays. */ + private final AndroidClock clock; + + private Logger logger; + + /** Id of the Ticl for which this scheduler will process events. */ + private long ticlId = -1; + + AndroidInternalScheduler(Context context, AndroidClock clock) { + this.context = Preconditions.checkNotNull(context); + this.clock = Preconditions.checkNotNull(clock); + } + + @Override + public void setSystemResources(SystemResources resources) { + this.logger = Preconditions.checkNotNull(resources.getLogger()); + } + + @Override + public void schedule(int delayMs, Runnable runnable) { + if (!(runnable instanceof NamedRunnable)) { + throw new RuntimeException("Unsupported: can only schedule named runnables, not " + runnable); + } + // Create an intent that will cause the service to run the right recurring task. We explicitly + // target it to our AlarmReceiver so that no other process in the system can receive it and so + // that our AlarmReceiver will not be able to receive events from any other broadcaster (which + // it would be if we used action-based targeting). + String taskName = ((NamedRunnable) runnable).getName(); + Intent eventIntent = ProtocolIntents.newSchedulerIntent(taskName, ticlId); + eventIntent.setClass(context, AlarmReceiver.class); + + // Create a pending intent that will cause the AlarmManager to fire the above intent. + PendingIntent sender = PendingIntent.getBroadcast(context, + (int) (Integer.MAX_VALUE * Math.random()), eventIntent, PendingIntent.FLAG_ONE_SHOT); + + // Schedule the pending intent after the appropriate delay. + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + long executeMs = clock.nowMs() + delayMs; + alarmManager.set(AlarmManager.RTC, executeMs, sender); + } + + /** + * Handles an event intent created in {@link #schedule} by running the corresponding recurring + * task. + * <p> + * REQUIRES: a recurring task with the name in the intent be present in {@link #registeredTasks}. + */ + void handleSchedulerEvent(AndroidSchedulerEvent event) { + Runnable recurringTaskRunnable = TypedUtil.mapGet(registeredTasks, event.getEventName()); + if (recurringTaskRunnable == null) { + throw new NullPointerException("No task registered for " + event.getEventName()); + } + if (ticlId != event.getTiclId()) { + logger.warning("Ignoring event with wrong ticl id (not %s): %s", ticlId, event); + return; + } + recurringTaskRunnable.run(); + } + + /** + * Registers {@code task} so that it can be subsequently run by the scheduler. + * <p> + * REQUIRES: no recurring task with the same name be already present in {@link #registeredTasks}. + */ + void registerTask(String name, Runnable runnable) { + Runnable previous = registeredTasks.put(name, runnable); + if (previous != null) { + String message = new StringBuilder() + .append("Cannot overwrite task registered on ") + .append(name) + .append(", ") + .append(this) + .append("; tasks = ") + .append(registeredTasks.keySet()) + .toString(); + throw new IllegalStateException(message); + } + } + + @Override + public boolean isRunningOnThread() { + if (!checkStackForTest) { + return true; + } + // If requested, check that the current stack looks legitimate. + for (StackTraceElement stackElement : Thread.currentThread().getStackTrace()) { + if (stackElement.getMethodName().equals("onHandleIntent") && + stackElement.getClassName().contains("TiclService")) { + // Called from the TiclService. + return true; + } + if (stackElement.getClassName().equals(TESTABLE_CLIENT_CLASSNAME_FOR_TEST)) { + // Called from the TestableClient. + return true; + } + } + return false; + } + + @Override + public long getCurrentTimeMs() { + return clock.nowMs(); + } + + /** Removes the registered tasks. */ + void reset() { + logger.fine("Clearing registered tasks on %s", this); + registeredTasks.clear(); + } + + /** + * Sets the id of the ticl for which this scheduler will process events. We do not know the + * Ticl id until done constructing the Ticl, and we need the scheduler to construct a Ticl. This + * method breaks what would otherwise be a dependency cycle on getting the Ticl id. + */ + void setTiclId(long ticlId) { + this.ticlId = ticlId; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationClientImpl.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationClientImpl.java new file mode 100644 index 0000000..a6cecb1 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationClientImpl.java @@ -0,0 +1,260 @@ +/* + * 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.external.client.InvalidationClient; +import com.google.ipc.invalidation.external.client.InvalidationListener; +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.external.client.types.Invalidation; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.InvalidationClientCore; +import com.google.ipc.invalidation.ticl.ProtoWrapperConverter; +import com.google.ipc.invalidation.ticl.android2.ProtocolIntents.ListenerUpcalls; +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState; +import com.google.ipc.invalidation.ticl.proto.Client.AckHandleP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; +import com.google.ipc.invalidation.util.Preconditions; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; + +import java.util.Arrays; +import java.util.Map; +import java.util.Random; + + +/** + * Android specialization of {@link InvalidationClientCore}. Configures the internal scheduler of + * the provided resources with references to the recurring tasks in the Ticl and also provides + * an {@link InvalidationListener} instance to the Ticl that will forward upcalls to the + * actual application listener using {@link Intent}s. + * <p> + * This class requires that {@code SystemResources} {@code Storage} implementations be synchronous. + * I.e., they must invoke their callbacks inline. We require this because it is very difficult + * to handle asynchrony in an Android {@code IntentService}. Every async point requires marshalling + * the Ticl state to disk. Additionally, we must be able to resume processing where we left off; + * i.e., we must be able to (morally) save the value of the program counter. Intents, unlike Java + * callbacks, do not implicitly save the PC value, so we need to manually encode it in Intent + * data. This is extremely awkward, so we avoid asynchrony in the storage API. + * + */ +class AndroidInvalidationClientImpl extends InvalidationClientCore { + /** Class implementing the application listener stub (allows overriding default for tests). */ + static Class<? extends Service> listenerServiceClassForTest = null; + + /** + * {@link InvalidationListener} implementation that forwards all calls to a remote listener + * using Android intents. + */ + static class IntentForwardingListener implements InvalidationListener { + + /** Android system context. */ + private final Context context; + + /** Logger from Ticl resources. */ + private final Logger logger; + + IntentForwardingListener(Context context, Logger logger) { + this.context = Preconditions.checkNotNull(context); + this.logger = Preconditions.checkNotNull(logger); + } + + // All calls are implemented by marshalling the arguments to an Intent and sending the Intent + // to the application. + + @Override + public void ready(InvalidationClient client) { + issueIntent(context, ListenerUpcalls.newReadyIntent()); + } + + @Override + public void invalidate(InvalidationClient client, Invalidation invalidation, + AckHandle ackHandle) { + try { + AckHandleP ackHandleP = AckHandleP.parseFrom(ackHandle.getHandleData()); + issueIntent(context, ListenerUpcalls.newInvalidateIntent( + ProtoWrapperConverter.convertToInvalidationProto(invalidation), ackHandleP)); + } catch (ValidationException exception) { + // Log and drop invalid call. + logBadAckHandle("invalidate", ackHandle); + } + } + + @Override + public void invalidateUnknownVersion(InvalidationClient client, ObjectId objectId, + AckHandle ackHandle) { + try { + AckHandleP ackHandleP = AckHandleP.parseFrom(ackHandle.getHandleData()); + issueIntent(context, ListenerUpcalls.newInvalidateUnknownIntent( + ProtoWrapperConverter.convertToObjectIdProto(objectId), ackHandleP)); + } catch (ValidationException exception) { + // Log and drop invalid call. + logBadAckHandle("invalidateUnknownVersion", ackHandle); + } + } + + @Override + public void invalidateAll(InvalidationClient client, AckHandle ackHandle) { + try { + AckHandleP ackHandleP = AckHandleP.parseFrom(ackHandle.getHandleData()); + issueIntent(context, ListenerUpcalls.newInvalidateAllIntent(ackHandleP)); + } catch (ValidationException exception) { + // Log and drop invalid call. + logBadAckHandle("invalidateAll", ackHandle); + } + } + + @Override + public void informRegistrationStatus( + InvalidationClient client, ObjectId objectId, RegistrationState regState) { + Intent intent = ListenerUpcalls.newRegistrationStatusIntent( + ProtoWrapperConverter.convertToObjectIdProto(objectId), + regState == RegistrationState.REGISTERED); + issueIntent(context, intent); + } + + @Override + public void informRegistrationFailure(InvalidationClient client, ObjectId objectId, + boolean isTransient, String errorMessage) { + issueIntent(context, ListenerUpcalls.newRegistrationFailureIntent( + ProtoWrapperConverter.convertToObjectIdProto(objectId), isTransient, errorMessage)); + } + + @Override + public void reissueRegistrations(InvalidationClient client, byte[] prefix, int prefixLength) { + issueIntent(context, ListenerUpcalls.newReissueRegistrationsIntent(prefix, prefixLength)); + } + + @Override + public void informError(InvalidationClient client, ErrorInfo errorInfo) { + issueIntent(context, ListenerUpcalls.newErrorIntent(errorInfo)); + } + + /** + * Sends {@code intent} to the real listener via the listener intent service class. + */ + static void issueIntent(Context context, Intent intent) { + intent.setClassName(context, (listenerServiceClassForTest != null) ? + listenerServiceClassForTest.getName() : + new AndroidTiclManifest(context).getListenerServiceClass()); + context.startService(intent); + } + + /** + * Logs a warning that a listener upcall to {@code method} has been dropped because + * {@code unparseableHandle} could not be parsed. + */ + private void logBadAckHandle(String method, AckHandle unparseableHandle) { + logger.warning("Dropping call to %s; could not parse ack handle data %s", + method, Arrays.toString(unparseableHandle.getHandleData())); + } + } + + /** + * Unique identifier for this Ticl. This is used to ensure that scheduler intents for other Ticls + * are not incorrectly delivered to this instance. + */ + private final long schedulingId; + + /** + * Creates a fresh instance. + * + * @param context Android system context + * @param resources Ticl resources to use + * @param random random number generator for the Ticl + * @param clientType type of the Ticl + * @param clientName unique application name for the Ticl + * @param config configuration to use + */ + AndroidInvalidationClientImpl(Context context, SystemResources resources, Random random, + int clientType, byte[] clientName, ClientConfigP config) { + super(resources, random, clientType, clientName, config, getApplicationName(context), + new IntentForwardingListener(context, resources.getLogger())); + this.schedulingId = resources.getInternalScheduler().getCurrentTimeMs(); + resources.getLogger().fine("Create new Ticl scheduling id: %s", schedulingId); + initializeSchedulerWithRecurringTasks(); + } + + /** + * Creates an instance with state restored from {@code marshalledState}. Other parameters are as + * in {@link InvalidationClientCore}. + */ + AndroidInvalidationClientImpl(Context context, SystemResources resources, Random random, + AndroidTiclState marshalledState) { + super(resources, + random, + marshalledState.getMetadata().getClientType(), + marshalledState.getMetadata().getClientName().getByteArray(), + marshalledState.getMetadata().getClientConfig(), + getApplicationName(context), + marshalledState.getTiclState(), + new IntentForwardingListener(context, resources.getLogger())); + this.schedulingId = marshalledState.getMetadata().getTiclId(); + initializeSchedulerWithRecurringTasks(); + } + + /** Returns the name of the application using the Ticl. */ + private static String getApplicationName(Context context) { + return context.getPackageName(); + } + + /** + * Provides the internal scheduler with references to each of the recurring tasks that can be + * executed. + */ + private void initializeSchedulerWithRecurringTasks() { + if (!(getResources().getInternalScheduler() instanceof AndroidInternalScheduler)) { + throw new IllegalStateException("Scheduler must be an AndroidInternalScheduler, not " + + getResources().getInternalScheduler()); + } + AndroidInternalScheduler scheduler = + (AndroidInternalScheduler) getResources().getInternalScheduler(); + for (Map.Entry<String, Runnable> entry : getRecurringTasks().entrySet()) { + scheduler.registerTask(entry.getKey(), entry.getValue()); + } + } + + /** Returns the scheduling id of this Ticl. */ + long getSchedulingId() { + return schedulingId; + } + + // This method appears to serve no purpose, since it's just a delegation to the superclass method + // with the same access level (protected). However, protected also implies package access, so what + // this is doing is making this method visible to TiclStateManager. + @Override + protected ApplicationClientIdP getApplicationClientIdP() { + return super.getApplicationClientIdP(); + } + + // Similar rationale as getApplicationClientIdP. + @Override + protected ClientConfigP getConfig() { + return super.getConfig(); + } + + // Similar rationale as getApplicationClientIdP. + @Override + protected boolean isStarted() { + return super.isStarted(); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationClientStub.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationClientStub.java new file mode 100644 index 0000000..1683eda --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationClientStub.java @@ -0,0 +1,108 @@ +/* + * 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.external.client.InvalidationClient; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.ticl.ProtoWrapperConverter; +import com.google.ipc.invalidation.ticl.android2.ProtocolIntents.ClientDowncalls; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.util.Preconditions; + +import android.content.Context; +import android.content.Intent; + +import java.util.Collection; +import java.util.Collections; + +/** + * Implementation of {@link InvalidationClient} that uses intents to send commands to an Android + * service hosting the actual Ticl. This class is a proxy for the Android service. + * + */ +class AndroidInvalidationClientStub implements InvalidationClient { + /** Android system context. */ + private final Context context; + + /** Class implementing the Ticl service. */ + private final String serviceClass; + + /** logger. */ + private final Logger logger; + + /** Creates an instance from {@code context} and {@code logger}. */ + AndroidInvalidationClientStub(Context context, Logger logger) { + this.context = Preconditions.checkNotNull(context.getApplicationContext()); + this.logger = Preconditions.checkNotNull(logger); + this.serviceClass = new AndroidTiclManifest(context).getTiclServiceClass(); + } + + @Override + public void start() { + throw new UnsupportedOperationException( + "Android clients are automatically started when created"); + } + + // All calls work by marshalling the arguments to an Intent and sending the Intent to the Ticl + // service. + + @Override + public void stop() { + issueIntent(ClientDowncalls.newStopIntent()); + } + + @Override + public void register(ObjectId objectId) { + Collection<ObjectIdP> objects = + Collections.singletonList(ProtoWrapperConverter.convertToObjectIdProto(objectId)); + issueIntent(ClientDowncalls.newRegistrationIntent(objects)); + } + + @Override + public void register(Collection<ObjectId> objectIds) { + Collection<ObjectIdP> objectIdPs = + ProtoWrapperConverter.convertToObjectIdProtoCollection(objectIds); + issueIntent(ClientDowncalls.newRegistrationIntent(objectIdPs)); + } + + @Override + public void unregister(ObjectId objectId) { + Collection<ObjectIdP> objects = + Collections.singletonList(ProtoWrapperConverter.convertToObjectIdProto(objectId)); + issueIntent(ClientDowncalls.newUnregistrationIntent(objects)); + } + + @Override + public void unregister(Collection<ObjectId> objectIds) { + Collection<ObjectIdP> objectIdPs = + ProtoWrapperConverter.convertToObjectIdProtoCollection(objectIds); + issueIntent(ClientDowncalls.newUnregistrationIntent(objectIdPs)); + } + + @Override + public void acknowledge(AckHandle ackHandle) { + issueIntent(ClientDowncalls.newAcknowledgeIntent(ackHandle.getHandleData())); + } + + /** Sends {@code intent} to the service implemented by {@link #serviceClass}. */ + private void issueIntent(Intent intent) { + intent.setClassName(context, serviceClass); + context.startService(intent); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationListenerIntentMapper.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationListenerIntentMapper.java new file mode 100644 index 0000000..ed1ec51 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationListenerIntentMapper.java @@ -0,0 +1,153 @@ +/* + * 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.external.client.InvalidationClient; +import com.google.ipc.invalidation.external.client.InvalidationListener; +import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.ticl.ProtoWrapperConverter; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ErrorUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.InvalidateUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationFailureUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationStatusUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReissueRegistrationsUpcall; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; + +import android.content.Context; +import android.content.Intent; + +import java.util.Arrays; + + +/** + * Routes intents to the appropriate methods in {@link InvalidationListener}. Typically, an instance + * of the mapper should be created in {@code IntentService#onCreate} and the {@link #handleIntent} + * method called in {@code IntentService#onHandleIntent}. + * + */ +public final class AndroidInvalidationListenerIntentMapper { + + /** The logger. */ + private final AndroidLogger logger = AndroidLogger.forPrefix(""); + + /** Client passed to the listener (supports downcalls). */ + public final InvalidationClient client; + + /** Listener to which intents are routed. */ + private final InvalidationListener listener; + + /** + * Initializes + * + * @param listener the listener to which intents should be routed + * @param context the context used by the listener to issue downcalls to the TICL + */ + public AndroidInvalidationListenerIntentMapper(InvalidationListener listener, Context context) { + client = new AndroidInvalidationClientStub(context, logger); + this.listener = listener; + } + + /** + * Handles a listener upcall by decoding the protocol buffer in {@code intent} and dispatching + * to the appropriate method on the {@link #listener}. + */ + public void handleIntent(Intent intent) { + // TODO: use wakelocks + + // Unmarshall the arguments from the Intent and make the appropriate call on the listener. + ListenerUpcall upcall = tryParseIntent(intent); + if (upcall == null) { + return; + } + + if (upcall.hasReady()) { + listener.ready(client); + } else if (upcall.getNullableInvalidate() != null) { + // Handle all invalidation-related upcalls on a common path, since they require creating + // an AckHandleP. + onInvalidateUpcall(upcall.getNullableInvalidate(), listener); + } else if (upcall.getNullableRegistrationStatus() != null) { + RegistrationStatusUpcall regStatus = upcall.getNullableRegistrationStatus(); + listener.informRegistrationStatus(client, + ProtoWrapperConverter.convertFromObjectIdProto(regStatus.getObjectId()), + regStatus.getIsRegistered() ? + RegistrationState.REGISTERED : RegistrationState.UNREGISTERED); + } else if (upcall.getNullableRegistrationFailure() != null) { + RegistrationFailureUpcall failure = upcall.getNullableRegistrationFailure(); + listener.informRegistrationFailure(client, + ProtoWrapperConverter.convertFromObjectIdProto(failure.getObjectId()), + failure.getTransient(), + failure.getMessage()); + } else if (upcall.getNullableReissueRegistrations() != null) { + ReissueRegistrationsUpcall reissueRegs = upcall.getNullableReissueRegistrations(); + listener.reissueRegistrations(client, reissueRegs.getPrefix().getByteArray(), + reissueRegs.getLength()); + } else if (upcall.getNullableError() != null) { + ErrorUpcall error = upcall.getNullableError(); + ErrorInfo errorInfo = ErrorInfo.newInstance(error.getErrorCode(), error.getIsTransient(), + error.getErrorMessage(), null); + listener.informError(client, errorInfo); + } else { + logger.warning("Dropping listener Intent with unknown call: %s", upcall); + } + } + + /** + * Handles an invalidation-related listener {@code upcall} by dispatching to the appropriate + * method on an instance of {@link InvalidationListener}. + */ + private void onInvalidateUpcall(InvalidateUpcall invalidate, InvalidationListener listener) { + AckHandle ackHandle = AckHandle.newInstance(invalidate.getAckHandle().getByteArray()); + if (invalidate.getNullableInvalidation() != null) { + listener.invalidate(client, + ProtoWrapperConverter.convertFromInvalidationProto(invalidate.getNullableInvalidation()), + ackHandle); + } else if (invalidate.hasInvalidateAll()) { + listener.invalidateAll(client, ackHandle); + } else if (invalidate.getNullableInvalidateUnknown() != null) { + listener.invalidateUnknownVersion(client, + ProtoWrapperConverter.convertFromObjectIdProto(invalidate.getNullableInvalidateUnknown()), + ackHandle); + } else { + throw new RuntimeException("Invalid invalidate upcall: " + invalidate); + } + } + + /** + * Returns a valid {@link ListenerUpcall} from {@code intent}, or {@code null} if one + * could not be parsed. + */ + private ListenerUpcall tryParseIntent(Intent intent) { + if (intent == null) { + return null; + } + byte[] upcallBytes = intent.getByteArrayExtra(ProtocolIntents.LISTENER_UPCALL_KEY); + if (upcallBytes == null) { + return null; + } + try { + ListenerUpcall upcall = ListenerUpcall.parseFrom(upcallBytes); + return upcall; + } catch (ValidationException exception) { + logger.severe("Could not parse listener upcall from %s", Arrays.toString(upcallBytes)); + return null; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationListenerStub.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationListenerStub.java new file mode 100644 index 0000000..527157d --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationListenerStub.java @@ -0,0 +1,84 @@ +/* + * 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.external.client.InvalidationListener; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; + +import android.app.IntentService; +import android.content.Intent; + + +/** + * Class implementing the {@link InvalidationListener} in the application using the client. + * This class is configured with the name of the application class implementing the + * {@link InvalidationListener} for the application. It receives upcalls from the Ticl as + * {@link Intent}s and dispatches them against dynamically created instances of the provided + * class. In this way, it serves as a bridge between the intent protocol and the application. + */ +public class AndroidInvalidationListenerStub extends IntentService { + /* This class needs to be public so that the Android runtime can start it as a service. */ + + private final AndroidLogger logger = AndroidLogger.forPrefix(""); + + /** The mapper used to route intents to the invalidation listener. */ + private AndroidInvalidationListenerIntentMapper intentMapper; + + public AndroidInvalidationListenerStub() { + super(""); + setIntentRedelivery(true); + } + + @Override + public void onCreate() { + super.onCreate(); + InvalidationListener listener = createListener(getListenerClass()); + intentMapper = new AndroidInvalidationListenerIntentMapper(listener, getApplicationContext()); + } + + @SuppressWarnings("unchecked") + private Class<? extends InvalidationListener> getListenerClass() { + try { + // Find the listener class that the application wants to use to receive upcalls. + return (Class<? extends InvalidationListener>) + Class.forName(new AndroidTiclManifest(this).getListenerClass()); + } catch (ClassNotFoundException exception) { + throw new RuntimeException("Invalid listener class", exception); + } + } + + /** + * Handles a listener upcall by decoding the protocol buffer in {@code intent} and dispatching + * to the appropriate method on an instance of {@link InvalidationListener}. + */ + @Override + public void onHandleIntent(Intent intent) { + logger.fine("onHandleIntent({0})", intent); + intentMapper.handleIntent(intent); + } + + private InvalidationListener createListener(Class<? extends InvalidationListener> listenerClass) { + // Create an instance of the application listener class to handle the upcall. + try { + return listenerClass.newInstance(); + } catch (InstantiationException exception) { + throw new RuntimeException("Could not create listener", exception); + } catch (IllegalAccessException exception) { + throw new RuntimeException("Could not create listener", exception); + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidManifest.xml b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidManifest.xml new file mode 100644 index 0000000..4d66538 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidManifest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- Copyright 2011 Google Inc. All Rights Reserved. --> + <!-- Test application for Android Client API and implementation. --> + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.ipc.invalidation.ticl.android2.tests" + android:versionName="2.3.0"> + <!--Unit test runner application --> + <application> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation + android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.google.ipc.invalidation.ticl.android2"/> + </manifest> diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidStorage.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidStorage.java new file mode 100644 index 0000000..a98ec2a --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidStorage.java @@ -0,0 +1,164 @@ +/* + * 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.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Storage; +import com.google.ipc.invalidation.external.client.types.Callback; +import com.google.ipc.invalidation.external.client.types.SimplePair; +import com.google.ipc.invalidation.external.client.types.Status; +import com.google.ipc.invalidation.ticl.InvalidationClientCore; +import com.google.ipc.invalidation.util.Preconditions; + +import android.content.Context; + +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Implementation of {@link Storage} for the Android Ticl. This implementation supports only + * the {@link InvalidationClientCore#CLIENT_TOKEN_KEY}. As required by Android storage + * implementations, it executes all callbacks synchronously. + * + */ +public class AndroidStorage implements Storage { + /** Name of the file in which state will be stored. */ + private static final String STATE_FILENAME = "ticl_storage.bin"; + + /** Maximum size of the file which we are willing to read. */ + private static final int MAX_STATE_FILE_SIZE_BYTES = 4096; + + private final Context context; + + public AndroidStorage(Context context) { + this.context = Preconditions.checkNotNull(context); + } + + @Override + public void setSystemResources(SystemResources resources) { + } + + @Override + public void writeKey(String key, byte[] value, Callback<Status> done) { + // We only support the CLIENT_TOKEN_KEY. + if (!key.equals(InvalidationClientCore.CLIENT_TOKEN_KEY)) { + done.accept(Status.newInstance(Status.Code.PERMANENT_FAILURE, "Key unsupported: " + key)); + return; + } + // Write the data. + FileOutputStream outstream = null; + Status status = null; + try { + outstream = context.openFileOutput(STATE_FILENAME, Context.MODE_PRIVATE); + outstream.write(value); + status = Status.newInstance(Status.Code.SUCCESS, ""); + } catch (FileNotFoundException exception) { + status = Status.newInstance(Status.Code.PERMANENT_FAILURE, "File not found: " + exception); + } catch (IOException exception) { + status = Status.newInstance(Status.Code.PERMANENT_FAILURE, "File not found: " + exception); + } finally { + if (outstream != null) { + try { + outstream.close(); + } catch (IOException exception) { + status = Status.newInstance( + Status.Code.PERMANENT_FAILURE, "Failed to close file: " + exception); + } + } + } + done.accept(status); + } + + @Override + public void readKey(String key, Callback<SimplePair<Status, byte[]>> done) { + // We only support the CLIENT_TOKEN_KEY. + if (!key.equals(InvalidationClientCore.CLIENT_TOKEN_KEY)) { + Status status = Status.newInstance(Status.Code.PERMANENT_FAILURE, "Key unsupported: " + key); + done.accept(SimplePair.of(status, (byte[]) null)); + return; + } + // Read and return the data. + FileInputStream instream = null; + SimplePair<Status, byte[]> result = null; + try { + instream = context.openFileInput(STATE_FILENAME); + long fileSizeBytes = instream.getChannel().size(); + if (fileSizeBytes > MAX_STATE_FILE_SIZE_BYTES) { + Status status = + Status.newInstance(Status.Code.PERMANENT_FAILURE, "File too big: " + fileSizeBytes); + result = SimplePair.of(status, (byte[]) null); + } + // Cast to int must be safe due to the above size check. + DataInputStream input = new DataInputStream(instream); + byte[] fileData = new byte[(int) fileSizeBytes]; + input.readFully(fileData); + result = SimplePair.of(Status.newInstance(Status.Code.SUCCESS, ""), fileData); + } catch (FileNotFoundException exception) { + Status status = + Status.newInstance(Status.Code.PERMANENT_FAILURE, "File not found: " + exception); + result = SimplePair.of(status, (byte[]) null); + } catch (IOException exception) { + Status status = + Status.newInstance(Status.Code.TRANSIENT_FAILURE, "IO exception: " + exception); + result = SimplePair.of(status, (byte[]) null); + } finally { + if (instream != null) { + try { + instream.close(); + } catch (IOException exception) { + Status status = + Status.newInstance( + Status.Code.TRANSIENT_FAILURE, "Failed to close file: " + exception); + result = SimplePair.of(status, (byte[]) null); + } + } + } + done.accept(result); + } + + @Override + public void deleteKey(String key, Callback<Boolean> done) { + // We only support the CLIENT_TOKEN_KEY. + if (!key.equals(InvalidationClientCore.CLIENT_TOKEN_KEY)) { + done.accept(false); + return; + } + if (!context.getFileStreamPath(STATE_FILENAME).exists()) { + // Deletion "succeeds" if the key didn't exist. + done.accept(true); + } else { + // Otherwise it succeeds based on whether the IO operation succeeded. + done.accept(context.deleteFile(STATE_FILENAME)); + } + } + + @Override + public void readAllKeys(Callback<SimplePair<Status, String>> keyCallback) { + // If the state file exists, supply the CLIENT_TOKEN_KEY as a present key. + if (context.getFileStreamPath(STATE_FILENAME).exists()) { + Status status = Status.newInstance(Status.Code.SUCCESS, ""); + keyCallback.accept(SimplePair.of(status, InvalidationClientCore.CLIENT_TOKEN_KEY)); + } + keyCallback.accept(null); + } + + static void deleteStateForTest(Context context) { + context.deleteFile(STATE_FILENAME); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidTiclManifest.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidTiclManifest.java new file mode 100644 index 0000000..1f4ab07 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidTiclManifest.java @@ -0,0 +1,163 @@ +/* + * 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.util.Preconditions; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Interface to the {@code AndroidManifest.xml} that provides access to the configuration data + * required by the Android Ticl. + * + */ +public class AndroidTiclManifest { + + /** + * Cache of {@link ApplicationMetadata} to avoid repeatedly scanning manifest. The key is the + * package name for the context. + */ + private static final Map<String, ApplicationMetadata> applicationMetadataCache = + new HashMap<String, ApplicationMetadata>(); + + /** Application metadata from the Android manifest. */ + private final ApplicationMetadata metadata; + + public AndroidTiclManifest(Context context) { + metadata = createApplicationMetadata(Preconditions.checkNotNull(context)); + } + + /** Returns the name of the class implementing the Ticl service. */ + public String getTiclServiceClass() { + return metadata.ticlServiceClass; + } + + /** Returns the name of the class on which listener events will be invoked. */ + String getListenerClass() { + return metadata.listenerClass; + } + + /** Returns the name of the class implementing the invalidation listener intent service. */ + public String getListenerServiceClass() { + return metadata.listenerServiceClass; + } + + /** + * Returns the name of the class implementing the background invalidation listener intent service. + */ + String getBackgroundInvalidationListenerServiceClass() { + return metadata.backgroundInvalidationListenerServiceClass; + } + + /** + * If it has not already been cached for the given {@code context}, creates and caches application + * metadata from the manifest. + */ + private static ApplicationMetadata createApplicationMetadata(Context context) { + synchronized (applicationMetadataCache) { + String packageName = context.getPackageName(); + ApplicationMetadata metadata = applicationMetadataCache.get(packageName); + if (metadata == null) { + metadata = new ApplicationMetadata(context); + applicationMetadataCache.put(packageName, metadata); + } + return metadata; + } + } + + /** Application metadata for a specific context. */ + private static final class ApplicationMetadata { + /** + * Name of the {@code <application>} metadata element whose value gives the Java class that + * implements the application {@code InvalidationListener}. Must be set if + * {@link #LISTENER_SERVICE_NAME_KEY} is not set. + */ + private static final String LISTENER_NAME_KEY = "ipc.invalidation.ticl.listener_class"; + + /** + * Name of the {@code <application>} metadata element whose value gives the Java class that + * implements the Ticl service. Should only be set in tests. + */ + private static final String TICL_SERVICE_NAME_KEY = "ipc.invalidation.ticl.service_class"; + + /** + * Name of the {@code <application>} metadata element whose value gives the Java class that + * implements the application's invalidation listener intent service. + */ + private static final String LISTENER_SERVICE_NAME_KEY = + "ipc.invalidation.ticl.listener_service_class"; + + /** + * Name of the {@code <application>} metadata element whose value gives the Java class that + * implements the application's background invalidation listener intent service. + */ + private static final String BACKGROUND_INVALIDATION_LISTENER_SERVICE_NAME_KEY = + "ipc.invalidation.ticl.background_invalidation_listener_service_class"; + + /** Default values returned if not overriden by the manifest file. */ + private static final Map<String, String> DEFAULTS = new HashMap<String, String>(); + static { + DEFAULTS.put(TICL_SERVICE_NAME_KEY, + "com.google.ipc.invalidation.ticl.android2.TiclService"); + DEFAULTS.put(LISTENER_NAME_KEY, ""); + DEFAULTS.put(LISTENER_SERVICE_NAME_KEY, + "com.google.ipc.invalidation.ticl.android2.AndroidInvalidationListenerStub"); + DEFAULTS.put(BACKGROUND_INVALIDATION_LISTENER_SERVICE_NAME_KEY, null); + } + + private final String ticlServiceClass; + private final String listenerClass; + private final String listenerServiceClass; + private final String backgroundInvalidationListenerServiceClass; + + ApplicationMetadata(Context context) { + ApplicationInfo appInfo; + try { + // Read metadata from manifest.xml + appInfo = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + } catch (NameNotFoundException exception) { + throw new RuntimeException("Cannot read own application info", exception); + } + ticlServiceClass = readApplicationMetadata(appInfo, TICL_SERVICE_NAME_KEY); + listenerClass = readApplicationMetadata(appInfo, LISTENER_NAME_KEY); + listenerServiceClass = readApplicationMetadata(appInfo, LISTENER_SERVICE_NAME_KEY); + backgroundInvalidationListenerServiceClass = + readApplicationMetadata(appInfo, BACKGROUND_INVALIDATION_LISTENER_SERVICE_NAME_KEY); + } + + /** + * Returns the metadata-provided value for {@code key} in {@code appInfo} if one + * exists, or the value from {@link #DEFAULTS} if one does not. + */ + private static String readApplicationMetadata(ApplicationInfo appInfo, String key) { + String value = null; + if (appInfo.metaData != null) { + value = appInfo.metaData.getString(key); + } + // Return the manifest value if present or the default value if not. + return (value != null) ? value : DEFAULTS.get(key); + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/ProtocolIntents.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/ProtocolIntents.java new file mode 100644 index 0000000..c5b19ca --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/ProtocolIntents.java @@ -0,0 +1,262 @@ +/* + * 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.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidNetworkSendRequest; +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidSchedulerEvent; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.AckDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StartDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StopDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient; +import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.NetworkStatus; +import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.ServerMessage; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ErrorUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.InvalidateUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReadyUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationFailureUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationStatusUpcall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReissueRegistrationsUpcall; +import com.google.ipc.invalidation.ticl.proto.Client.AckHandleP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version; +import com.google.ipc.invalidation.util.Bytes; + +import android.content.Intent; + +import java.util.Collection; + +/** + * Factory class for {@link Intent}s used between the application, Ticl, and listener in the + * Android Ticl. + * + */ +public class ProtocolIntents { + /** Version of the on-device protocol. */ + static final Version ANDROID_PROTOCOL_VERSION_VALUE = Version.create(1, 0); + + /** Key of Intent byte[] extra holding a client downcall protocol buffer. */ + public static final String CLIENT_DOWNCALL_KEY = "ipcinv-downcall"; + + /** Key of Intent byte[] extra holding an internal downcall protocol buffer. */ + public static final String INTERNAL_DOWNCALL_KEY = "ipcinv-internal-downcall"; + + /** Key of Intent byte[] extra holding a listener upcall protocol buffer. */ + public static final String LISTENER_UPCALL_KEY = "ipcinv-upcall"; + + /** Key of Intent byte[] extra holding a schedule event protocol buffer. */ + public static final String SCHEDULER_KEY = "ipcinv-scheduler"; + + /** Key of Intent byte[] extra holding an outbound message protocol buffer. */ + public static final String OUTBOUND_MESSAGE_KEY = "ipcinv-outbound-message"; + + /** Key of Intent byte[] extra holding an invalidation message protocol buffer. */ + public static final String BACKGROUND_INVALIDATION_KEY = "ipcinv-background-inv"; + + /** Intents corresponding to calls on {@code InvalidationClient}. */ + public static class ClientDowncalls { + public static Intent newStartIntent() { + Intent intent = new Intent(); + intent.putExtra(CLIENT_DOWNCALL_KEY, ClientDowncall.createWithStart( + ANDROID_PROTOCOL_VERSION_VALUE, StartDowncall.DEFAULT_INSTANCE).toByteArray()); + return intent; + } + + public static Intent newStopIntent() { + Intent intent = new Intent(); + intent.putExtra(CLIENT_DOWNCALL_KEY, ClientDowncall.createWithStop( + ANDROID_PROTOCOL_VERSION_VALUE, StopDowncall.DEFAULT_INSTANCE).toByteArray()); + return intent; + } + + public static Intent newAcknowledgeIntent(byte[] ackHandleData) { + AckDowncall ackDowncall = AckDowncall.create(new Bytes(ackHandleData)); + Intent intent = new Intent(); + intent.putExtra(CLIENT_DOWNCALL_KEY, ClientDowncall.createWithAck( + ANDROID_PROTOCOL_VERSION_VALUE, ackDowncall).toByteArray()); + return intent; + } + + public static Intent newRegistrationIntent(Collection<ObjectIdP> registrations) { + RegistrationDowncall regDowncall = + RegistrationDowncall.createWithRegistrations(registrations); + Intent intent = new Intent(); + intent.putExtra(CLIENT_DOWNCALL_KEY, ClientDowncall.createWithRegistrations( + ANDROID_PROTOCOL_VERSION_VALUE, regDowncall).toByteArray()); + return intent; + } + + public static Intent newUnregistrationIntent(Collection<ObjectIdP> unregistrations) { + RegistrationDowncall regDowncall = + RegistrationDowncall.createWithUnregistrations(unregistrations); + Intent intent = new Intent(); + intent.putExtra(CLIENT_DOWNCALL_KEY, ClientDowncall.createWithRegistrations( + ANDROID_PROTOCOL_VERSION_VALUE, regDowncall).toByteArray()); + return intent; + } + + private ClientDowncalls() { + // Disallow instantiation. + } + } + + /** Intents for non-public calls on the Ticl (currently, network-related calls. */ + public static class InternalDowncalls { + public static Intent newServerMessageIntent(Bytes serverMessage) { + Intent intent = new Intent(); + intent.putExtra(INTERNAL_DOWNCALL_KEY, InternalDowncall.createWithServerMessage( + ANDROID_PROTOCOL_VERSION_VALUE, ServerMessage.create(serverMessage)) + .toByteArray()); + return intent; + } + + public static Intent newNetworkStatusIntent(Boolean status) { + Intent intent = new Intent(); + intent.putExtra(INTERNAL_DOWNCALL_KEY, InternalDowncall.createWithNetworkStatus( + ANDROID_PROTOCOL_VERSION_VALUE, NetworkStatus.create(status)).toByteArray()); + return intent; + } + + public static Intent newNetworkAddrChangeIntent() { + Intent intent = new Intent(); + intent.putExtra(INTERNAL_DOWNCALL_KEY, InternalDowncall.createWithNetworkAddrChange( + ANDROID_PROTOCOL_VERSION_VALUE, true).toByteArray()); + return intent; + } + + public static Intent newCreateClientIntent(int clientType, Bytes clientName, + ClientConfigP config, boolean skipStartForTest) { + CreateClient createClient = + CreateClient.create(clientType, clientName, config, skipStartForTest); + Intent intent = new Intent(); + intent.putExtra(INTERNAL_DOWNCALL_KEY, InternalDowncall.createWithCreateClient( + ANDROID_PROTOCOL_VERSION_VALUE, createClient).toByteArray()); + return intent; + } + + private InternalDowncalls() { + // Disallow instantiation. + } + } + + /** Intents corresponding to calls on {@code InvalidationListener}. */ + public static class ListenerUpcalls { + public static Intent newReadyIntent() { + Intent intent = new Intent(); + intent.putExtra(LISTENER_UPCALL_KEY, ListenerUpcall.createWithReady( + ANDROID_PROTOCOL_VERSION_VALUE, ReadyUpcall.DEFAULT_INSTANCE).toByteArray()); + return intent; + } + + public static Intent newInvalidateIntent(InvalidationP invalidation, AckHandleP ackHandle) { + Intent intent = new Intent(); + InvalidateUpcall invUpcall = + InvalidateUpcall.createWithInvalidation(new Bytes(ackHandle.toByteArray()), invalidation); + intent.putExtra(LISTENER_UPCALL_KEY, ListenerUpcall.createWithInvalidate( + ANDROID_PROTOCOL_VERSION_VALUE, invUpcall).toByteArray()); + return intent; + } + + public static Intent newInvalidateUnknownIntent(ObjectIdP object, AckHandleP ackHandle) { + Intent intent = new Intent(); + InvalidateUpcall invUpcall = + InvalidateUpcall.createWithInvalidateUnknown(new Bytes(ackHandle.toByteArray()), object); + intent.putExtra(LISTENER_UPCALL_KEY, ListenerUpcall.createWithInvalidate( + ANDROID_PROTOCOL_VERSION_VALUE, invUpcall).toByteArray()); + return intent; + } + + public static Intent newInvalidateAllIntent(AckHandleP ackHandle) { + Intent intent = new Intent(); + InvalidateUpcall invUpcall = InvalidateUpcall.createWithInvalidateAll( + new Bytes(ackHandle.toByteArray()), true); + intent.putExtra(LISTENER_UPCALL_KEY, ListenerUpcall.createWithInvalidate( + ANDROID_PROTOCOL_VERSION_VALUE, invUpcall).toByteArray()); + return intent; + } + + public static Intent newRegistrationStatusIntent(ObjectIdP object, boolean isRegistered) { + Intent intent = new Intent(); + RegistrationStatusUpcall regUpcall = RegistrationStatusUpcall.create(object, isRegistered); + intent.putExtra(LISTENER_UPCALL_KEY, ListenerUpcall.createWithRegistrationStatus( + ANDROID_PROTOCOL_VERSION_VALUE, regUpcall).toByteArray()); + return intent; + } + + public static Intent newRegistrationFailureIntent(ObjectIdP object, boolean isTransient, + String message) { + Intent intent = new Intent(); + RegistrationFailureUpcall regUpcall = + RegistrationFailureUpcall.create(object, isTransient, message); + intent.putExtra(LISTENER_UPCALL_KEY, ListenerUpcall.createWithRegistrationFailure( + ANDROID_PROTOCOL_VERSION_VALUE, regUpcall).toByteArray()); + return intent; + } + + public static Intent newReissueRegistrationsIntent(byte[] prefix, int length) { + Intent intent = new Intent(); + ReissueRegistrationsUpcall reissueRegistrations = + ReissueRegistrationsUpcall.create(new Bytes(prefix), length); + intent.putExtra(LISTENER_UPCALL_KEY, ListenerUpcall.createWithReissueRegistrations( + ANDROID_PROTOCOL_VERSION_VALUE, reissueRegistrations).toByteArray()); + return intent; + } + + public static Intent newErrorIntent(ErrorInfo errorInfo) { + Intent intent = new Intent(); + ErrorUpcall errorUpcall = ErrorUpcall.create(errorInfo.getErrorReason(), + errorInfo.getErrorMessage(), errorInfo.isTransient()); + intent.putExtra(LISTENER_UPCALL_KEY, ListenerUpcall.createWithError( + ANDROID_PROTOCOL_VERSION_VALUE, errorUpcall).toByteArray()); + return intent; + } + + private ListenerUpcalls() { + // Disallow instantiation. + } + } + + /** Returns a new intent encoding a request to execute the scheduled action {@code eventName}. */ + public static Intent newSchedulerIntent(String eventName, long ticlId) { + byte[] eventBytes = AndroidSchedulerEvent.create(ANDROID_PROTOCOL_VERSION_VALUE, eventName, + ticlId).toByteArray(); + return new Intent().putExtra(SCHEDULER_KEY, eventBytes); + } + + /** Returns a new intent encoding a message to send to the data center. */ + public static Intent newOutboundMessageIntent(byte[] message) { + byte[] payloadBytes = AndroidNetworkSendRequest.create(ANDROID_PROTOCOL_VERSION_VALUE, + new Bytes(message)).toByteArray(); + return new Intent().putExtra(OUTBOUND_MESSAGE_KEY, payloadBytes); + } + + /** Returns a new intent encoding background invalidation messages. */ + public static Intent newBackgroundInvalidationIntent(InvalidationMessage invalidationMessage) { + byte[] payloadBytes = invalidationMessage.toByteArray(); + return new Intent().putExtra(BACKGROUND_INVALIDATION_KEY, payloadBytes); + } + + private ProtocolIntents() { + // Disallow instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/ResourcesFactory.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/ResourcesFactory.java new file mode 100644 index 0000000..067b243 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/ResourcesFactory.java @@ -0,0 +1,137 @@ +/* + * 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.external.client.SystemResources; +import com.google.ipc.invalidation.external.client.SystemResources.Scheduler; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.ticl.BasicSystemResources; +import com.google.ipc.invalidation.ticl.android2.channel.AndroidNetworkChannel; +import com.google.ipc.invalidation.util.Preconditions; + +import android.content.Context; + +/** + * Factory class for Android system resources. + * + */ +public class ResourcesFactory { + /** + * A scheduler that supports no operations. Used as the listener scheduler, which should never be + * called in Android. + */ + private static class InvalidScheduler implements Scheduler { + @Override + public void setSystemResources(SystemResources resources) { + } + + @Override + public void schedule(int delayMs, Runnable runnable) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isRunningOnThread() { + throw new UnsupportedOperationException(); + } + + @Override + public long getCurrentTimeMs() { + throw new UnsupportedOperationException(); + } + + } + + /** Implementation of {@link SystemResources} for the Android Ticl. */ + public static class AndroidResources extends BasicSystemResources { + /** Android system context. */ + private final Context context; + + /** Ticl-provided receiver for network events. */ + private NetworkChannel.NetworkListener networkListener; + + /** + * Creates an instance of resources for production code. + * + * @param context Android system context + * @param clock source of time for the internal scheduler + * @param logPrefix log prefix + */ + private AndroidResources(Context context, AndroidClock clock, String logPrefix) { + super(AndroidLogger.forPrefix(logPrefix), new AndroidInternalScheduler(context, clock), + new InvalidScheduler(), new AndroidNetworkChannel(context), new AndroidStorage(context), + getPlatformString()); + this.context = Preconditions.checkNotNull(context); + } + + /** Creates an instance for test from the provided resources and context. */ + + AndroidResources(Logger logger, AndroidInternalScheduler internalScheduler, + NetworkChannel network, Storage storage, Context context) { + super(logger, internalScheduler, new InvalidScheduler(), network, storage, + getPlatformString()); + this.context = Preconditions.checkNotNull(context); + } + + /** Returns the Android system context. */ + Context getContext() { + return context; + } + + /** + * Sets the network message listener provided by the Ticl. The network calls this method when + * the Ticl provides it with a listener; the Ticl service later retrieves the listener when + * it has a network event to communicate to the Ticl. + */ + public void setNetworkListener(NetworkChannel.NetworkListener networkListener) { + if (this.networkListener != null) { + throw new IllegalStateException("Listener already set: " + networkListener); + } + this.networkListener = Preconditions.checkNotNull(networkListener); + } + + /** Clears the network listener. */ + void clearNetworkListener() { + this.networkListener = null; + } + + /** Returns the network listener provided by the Ticl. */ + NetworkChannel.NetworkListener getNetworkListener() { + return Preconditions.checkNotNull(networkListener, "network listener not yet set"); + } + + /** Returns the platform string to use when constructing the resources. */ + private static String getPlatformString() { + return "Android-" + android.os.Build.VERSION.RELEASE; + } + } + + /** + * Creates a production instance. + * + * @param context Android system context + * @param clock source of time for the internal scheduler + * @param prefix log prefix + */ + static AndroidResources createResources(Context context, AndroidClock clock, String prefix) { + return new AndroidResources(context, clock, prefix); + } + + private ResourcesFactory() { + // Prevent instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java new file mode 100644 index 0000000..ff979f1 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java @@ -0,0 +1,389 @@ +/* + * 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.DigestFunction; +import com.google.ipc.invalidation.common.ObjectIdDigestUtils; +import com.google.ipc.invalidation.external.client.types.AckHandle; +import com.google.ipc.invalidation.external.client.types.Callback; +import com.google.ipc.invalidation.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.ipc.invalidation.external.client.types.SimplePair; +import com.google.ipc.invalidation.external.client.types.Status; +import com.google.ipc.invalidation.ticl.InvalidationClientCore; +import com.google.ipc.invalidation.ticl.PersistenceUtils; +import com.google.ipc.invalidation.ticl.ProtoWrapperConverter; +import com.google.ipc.invalidation.ticl.android2.AndroidInvalidationClientImpl.IntentForwardingListener; +import com.google.ipc.invalidation.ticl.android2.ResourcesFactory.AndroidResources; +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidSchedulerEvent; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall; +import com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient; +import com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerToClientMessage; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; + +import android.app.IntentService; +import android.content.Intent; + +import java.util.Collection; + + +/** + * An {@link IntentService} that manages a single Ticl. + * <p> + * Concurrency model: {@link IntentService} guarantees that calls to {@link #onHandleIntent} will + * be executed serially on a dedicated thread. They may perform blocking work without blocking + * the application calling the service. + * <p> + * This thread will be used as the internal-scheduler thread for the Ticl. + * + */ +public class TiclService extends IntentService { + /** This class must be public so that Android can instantiate it as a service. */ + + /** Resources for the created Ticls. */ + private AndroidResources resources; + + /** The function for computing persistence state digests when rewriting them. */ + private final DigestFunction digestFn = new ObjectIdDigestUtils.Sha1DigestFunction(); + + public TiclService() { + super("TiclService"); + + // If the process dies during a call to onHandleIntent, redeliver the intent when the service + // restarts. + setIntentRedelivery(true); + } + + /** + * Returns the resources to use for a Ticl. Normally, we use a new resources instance + * for every call, but for existing tests, we need to be able to override this function + * and return the same instance each time. + */ + AndroidResources createResources() { + return ResourcesFactory.createResources(this, new AndroidClock.SystemClock(), "TiclService"); + } + + @Override + protected void onHandleIntent(Intent intent) { + // TODO: We may want to use wakelocks to prevent the phone from sleeping + // before we have finished handling the Intent. + + if (intent == null) { + return; + } + + // We create resources anew each time. + resources = createResources(); + resources.start(); + resources.getLogger().fine("onHandleIntent(%s)", intent); + + try { + // Dispatch the appropriate handler function based on which extra key is set. + if (intent.hasExtra(ProtocolIntents.CLIENT_DOWNCALL_KEY)) { + handleClientDowncall(intent.getByteArrayExtra(ProtocolIntents.CLIENT_DOWNCALL_KEY)); + } else if (intent.hasExtra(ProtocolIntents.INTERNAL_DOWNCALL_KEY)) { + handleInternalDowncall(intent.getByteArrayExtra(ProtocolIntents.INTERNAL_DOWNCALL_KEY)); + } else if (intent.hasExtra(ProtocolIntents.SCHEDULER_KEY)) { + handleSchedulerEvent(intent.getByteArrayExtra(ProtocolIntents.SCHEDULER_KEY)); + } else { + resources.getLogger().warning("Received Intent without any recognized extras: %s", intent); + } + } finally { + // Null out resources to prevent accidentally using them in the future before they have been + // properly re-created. + resources.stop(); + resources = null; + } + } + + /** Handles a request to call a function on the ticl. */ + private void handleClientDowncall(byte[] clientDowncallBytes) { + // Parse and validate the request. + final ClientDowncall downcall; + try { + downcall = ClientDowncall.parseFrom(clientDowncallBytes); + } catch (ValidationException exception) { + resources.getLogger().warning("Failed parsing ClientDowncall from %s: %s", + Bytes.toLazyCompactString(clientDowncallBytes), exception.getMessage()); + return; + } + + resources.getLogger().fine("Handle client downcall: %s", downcall); + + // Restore the appropriate Ticl. + // TODO: what if this is the "wrong" Ticl? + AndroidInvalidationClientImpl ticl = loadExistingTicl(); + if (ticl == null) { + resources.getLogger().warning("Dropping client downcall since no Ticl: %s", downcall); + return; + } + + // Call the appropriate method. + if (downcall.getNullableAck() != null) { + ticl.acknowledge( + AckHandle.newInstance(downcall.getNullableAck().getAckHandle().getByteArray())); + } else if (downcall.hasStart()) { + ticl.start(); + } else if (downcall.hasStop()) { + ticl.stop(); + } else if (downcall.getNullableRegistrations() != null) { + RegistrationDowncall regDowncall = downcall.getNullableRegistrations(); + if (!regDowncall.getRegistrations().isEmpty()) { + Collection<ObjectId> objects = + ProtoWrapperConverter.convertFromObjectIdProtoCollection(regDowncall.getRegistrations()); + ticl.register(objects); + } + if (!regDowncall.getUnregistrations().isEmpty()) { + Collection<ObjectId> objects = ProtoWrapperConverter.convertFromObjectIdProtoCollection( + regDowncall.getUnregistrations()); + ticl.unregister(objects); + } + } else { + throw new RuntimeException("Invalid downcall passed validation: " + downcall); + } + // If we are stopping the Ticl, then just delete its persisted in-memory state, since no + // operations on a stopped Ticl are valid. Otherwise, save the Ticl in-memory state to + // stable storage. + if (downcall.hasStop()) { + TiclStateManager.deleteStateFile(this); + } else { + TiclStateManager.saveTicl(this, resources.getLogger(), ticl); + } + } + + /** Handles an internal downcall on the Ticl. */ + private void handleInternalDowncall(byte[] internalDowncallBytes) { + // Parse and validate the request. + final InternalDowncall downcall; + try { + downcall = InternalDowncall.parseFrom(internalDowncallBytes); + } catch (ValidationException exception) { + resources.getLogger().warning("Failed parsing InternalDowncall from %s: %s", + Bytes.toLazyCompactString(internalDowncallBytes), + exception.getMessage()); + return; + } + resources.getLogger().fine("Handle internal downcall: %s", downcall); + + // Message from the data center; just forward it to the Ticl. + if (downcall.getNullableServerMessage() != null) { + // We deliver the message regardless of whether the Ticl existed, since we'll want to + // rewrite persistent state in the case where it did not. + // TODO: what if this is the "wrong" Ticl? + AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources); + handleServerMessage((ticl != null), + downcall.getNullableServerMessage().getData().getByteArray()); + if (ticl != null) { + TiclStateManager.saveTicl(this, resources.getLogger(), ticl); + } + return; + } + + // Network online/offline status change; just forward it to the Ticl. + if (downcall.getNullableNetworkStatus() != null) { + // Network status changes only make sense for Ticls that do exist. + // TODO: what if this is the "wrong" Ticl? + AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources); + if (ticl != null) { + resources.getNetworkListener().onOnlineStatusChange( + downcall.getNullableNetworkStatus().getIsOnline()); + TiclStateManager.saveTicl(this, resources.getLogger(), ticl); + } + return; + } + + // Client network address change; just forward it to the Ticl. + if (downcall.getNetworkAddrChange()) { + AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources); + if (ticl != null) { + resources.getNetworkListener().onAddressChange(); + TiclStateManager.saveTicl(this, resources.getLogger(), ticl); + } + return; + } + + // Client creation request (meta operation). + if (downcall.getNullableCreateClient() != null) { + handleCreateClient(downcall.getNullableCreateClient()); + return; + } + throw new RuntimeException( + "Invalid internal downcall passed validation: " + downcall); + } + + /** Handles a {@code createClient} request. */ + private void handleCreateClient(CreateClient createClient) { + // Ensure no Ticl currently exists. + TiclStateManager.deleteStateFile(this); + + // Create the requested Ticl. + resources.getLogger().fine("Create client: creating"); + TiclStateManager.createTicl(this, resources, createClient.getClientType(), + createClient.getClientName().getByteArray(), createClient.getClientConfig(), + createClient.getSkipStartForTest()); + } + + /** + * Handles a {@code message} for a {@code ticl}. If the {@code ticl} is started, delivers the + * message. If the {@code ticl} is not started, drops the message and clears the last message send + * time in the Ticl persistent storage so that the Ticl will send a heartbeat the next time it + * starts. + */ + private void handleServerMessage(boolean isTiclStarted, byte[] message) { + if (isTiclStarted) { + // Normal case -- message for a started Ticl. Deliver the message. + resources.getNetworkListener().onMessageReceived(message); + return; + } + + // Even if the client is stopped, attempt to send invalidations if the client is configured to + // receive them. + maybeSendBackgroundInvalidationIntent(message); + + // The Ticl isn't started. Rewrite persistent storage so that the last-send-time is a long + // time ago. The next time the Ticl starts, it will send a message to the data center, which + // ensures that it will be marked online and that the dropped message (or an equivalent) will + // be delivered. + // Android storage implementations are required to execute callbacks inline, so this code + // all executes synchronously. + resources.getLogger().fine("Message for unstarted Ticl; rewrite state"); + resources.getStorage().readKey(InvalidationClientCore.CLIENT_TOKEN_KEY, + new Callback<SimplePair<Status, byte[]>>() { + @Override + public void accept(SimplePair<Status, byte[]> result) { + byte[] stateBytes = result.second; + if (stateBytes == null) { + resources.getLogger().info("No persistent state found for client; not rewriting"); + return; + } + // Create new state identical to the old state except with a cleared + // lastMessageSendTimeMs. + PersistentTiclState state = PersistenceUtils.deserializeState( + resources.getLogger(), stateBytes, digestFn); + if (state == null) { + resources.getLogger().warning("Ignoring invalid Ticl state: %s", + Bytes.toLazyCompactString(stateBytes)); + return; + } + PersistentTiclState.Builder stateBuilder = state.toBuilder(); + stateBuilder.lastMessageSendTimeMs = 0L; + state = stateBuilder.build(); + + // Serialize the new state and write it to storage. + byte[] newClientState = PersistenceUtils.serializeState(state, digestFn); + resources.getStorage().writeKey(InvalidationClientCore.CLIENT_TOKEN_KEY, newClientState, + new Callback<Status>() { + @Override + public void accept(Status status) { + if (status.getCode() != Status.Code.SUCCESS) { + resources.getLogger().warning( + "Failed saving rewritten persistent state to storage"); + } + } + }); + } + }); + } + + /** + * If a service is registered to handle them, forward invalidations received while the + * invalidation client is stopped. + */ + private void maybeSendBackgroundInvalidationIntent(byte[] message) { + // If a service is registered to receive background invalidations, parse the message to see if + // any of them should be forwarded. + AndroidTiclManifest manifest = new AndroidTiclManifest(getApplicationContext()); + String backgroundServiceClass = + manifest.getBackgroundInvalidationListenerServiceClass(); + if (backgroundServiceClass != null) { + try { + ServerToClientMessage s2cMessage = ServerToClientMessage.parseFrom(message); + if (s2cMessage.getNullableInvalidationMessage() != null) { + Intent intent = ProtocolIntents.newBackgroundInvalidationIntent( + s2cMessage.getNullableInvalidationMessage()); + intent.setClassName(getApplicationContext(), backgroundServiceClass); + startService(intent); + } + } catch (ValidationException exception) { + resources.getLogger().info("Failed to parse message: %s", exception.getMessage()); + } + } + } + + /** Handles a request to call a particular recurring task on the Ticl. */ + private void handleSchedulerEvent(byte[] schedulerEventBytes) { + // Parse and validate the request. + final AndroidSchedulerEvent event; + try { + event = AndroidSchedulerEvent.parseFrom(schedulerEventBytes); + } catch (ValidationException exception) { + resources.getLogger().warning("Failed parsing SchedulerEvent from %s: %s", + Bytes.toLazyCompactString(schedulerEventBytes), exception.getMessage()); + return; + } + + resources.getLogger().fine("Handle scheduler event: %s", event); + + // Restore the appropriate Ticl. + AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources); + + // If the Ticl didn't exist, drop the event. + if (ticl == null) { + resources.getLogger().fine("Dropping event %s; Ticl state does not exist", + event.getEventName()); + return; + } + + // Invoke the appropriate event. + AndroidInternalScheduler ticlScheduler = + (AndroidInternalScheduler) resources.getInternalScheduler(); + ticlScheduler.handleSchedulerEvent(event); + + // Save the Ticl state to persistent storage. + TiclStateManager.saveTicl(this, resources.getLogger(), ticl); + } + + /** + * Returns the existing Ticl from persistent storage, or {@code null} if it does not exist. + * If it does not exist, raises an error to the listener. This function should be used + * only when loading a Ticl in response to a client-application call, since it raises an error + * back to the application. + */ + private AndroidInvalidationClientImpl loadExistingTicl() { + AndroidInvalidationClientImpl ticl = TiclStateManager.restoreTicl(this, resources); + if (ticl == null) { + informListenerOfPermanentError("Client does not exist on downcall"); + } + return ticl; + } + + /** Informs the listener of a non-retryable {@code error}. */ + private void informListenerOfPermanentError(final String error) { + ErrorInfo errorInfo = ErrorInfo.newInstance(0, false, error, null); + Intent errorIntent = ProtocolIntents.ListenerUpcalls.newErrorIntent(errorInfo); + IntentForwardingListener.issueIntent(this, errorIntent); + } + + /** Returns the resources used for the current Ticl. */ + AndroidResources getSystemResourcesForTest() { + return resources; + } +} 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 0000000..bf9ded0 --- /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. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/WakeLockManager.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/WakeLockManager.java new file mode 100644 index 0000000..f3c3103 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/WakeLockManager.java @@ -0,0 +1,224 @@ +/* + * 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.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.util.Preconditions; + +import android.content.Context; +import android.os.Build; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Singleton that manages wake locks identified by a key. Wake locks are refcounted so if they are + * acquired multiple times with the same key they will not unlocked until they are released an + * equivalent number of times. + */ +public class WakeLockManager { + /** Logger. */ + private static final Logger logger = AndroidLogger.forTag("WakeLockMgr"); + + /** Lock over all state. Must be acquired by all non-private methods. */ + private static final Object LOCK = new Object(); + + /** + * SDK_INT version taken from android.BUILD.VERSION_CODE.ICE_CREAM_SANDWICH. We cannot reference + * the field directly because if it is not inlined by the Java compiler, it will not be available + * in the earlier versions of Android for which the version check in acquire() exists. + */ + private static final int ICE_CREAM_SANDWICH_VERSION_CODE = 14; + + /** Singleton instance. */ + private static WakeLockManager theManager; + + /** Wake locks by key. */ + private final Map<Object, PowerManager.WakeLock> wakeLocks = + new HashMap<Object, PowerManager.WakeLock>(); + + private final PowerManager powerManager; + + private final Context applicationContext; + + private WakeLockManager(Context context) { + powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + applicationContext = Preconditions.checkNotNull(context); + } + + /** Returns the wake lock manager. */ + public static WakeLockManager getInstance(Context context) { + Preconditions.checkNotNull(context); + Preconditions.checkNotNull(context.getApplicationContext()); + synchronized (LOCK) { + if (theManager == null) { + theManager = new WakeLockManager(context.getApplicationContext()); + } else { + if (theManager.applicationContext != context.getApplicationContext()) { + String message = new StringBuilder() + .append("Provided context ") + .append(context.getApplicationContext()) + .append("does not match stored context ") + .append(theManager.applicationContext) + .toString(); + throw new IllegalStateException(message); + } + } + return theManager; + } + } + + /** + * Acquires a wake lock identified by the {@code key} that will be automatically released after at + * most {@code timeoutMs}. + */ + public void acquire(Object key, int timeoutMs) { + synchronized (LOCK) { + cleanup(); + Preconditions.checkNotNull(key, "Key can not be null"); + + // Prior to ICS, acquiring a lock with a timeout and then explicitly releasing the lock + // results in runtime errors. We rely on the invalidation system correctly releasing locks + // rather than defensively requesting a timeout. + if (Build.VERSION.SDK_INT >= ICE_CREAM_SANDWICH_VERSION_CODE) { + log(key, "acquiring with timeout " + timeoutMs); + getWakeLock(key).acquire(timeoutMs); + } else { + log(key, "acquiring"); + getWakeLock(key).acquire(); + } + } + } + + /** + * Releases the wake lock identified by the {@code key} if it is currently held. + */ + public void release(Object key) { + synchronized (LOCK) { + cleanup(); + Preconditions.checkNotNull(key, "Key can not be null"); + PowerManager.WakeLock wakelock = getWakeLock(key); + + // If the lock is not held (if for instance there is a wake lock timeout), we cannot release + // again without triggering a RuntimeException. + if (!wakelock.isHeld()) { + logger.warning("Over-release of wakelock: %s", key); + return; + } + + // We held the wake lock recently, so it's likely safe to release it. Between the isHeld() + // check and the release() call, the wake lock may time out however and we catch the resulting + // RuntimeException. + try { + wakelock.release(); + } catch (RuntimeException exception) { + logger.warning("Over-release of wakelock: %s, %s", key, exception); + } + log(key, "released"); + + // Now if the lock is not held, that means we were the last holder, so we should remove it + // from the map. + if (!wakelock.isHeld()) { + wakeLocks.remove(key); + log(key, "freed"); + } + } + } + + /** + * Returns whether there is currently a wake lock held for the provided {@code key}. + */ + public boolean isHeld(Object key) { + synchronized (LOCK) { + cleanup(); + Preconditions.checkNotNull(key, "Key can not be null"); + if (!wakeLocks.containsKey(key)) { + return false; + } + return getWakeLock(key).isHeld(); + } + } + + /** Returns whether the manager has any active (held) wake locks. */ + + public boolean hasWakeLocks() { + synchronized (LOCK) { + cleanup(); + return !wakeLocks.isEmpty(); + } + } + + /** Discards (without releasing) all wake locks. */ + + public void resetForTest() { + synchronized (LOCK) { + cleanup(); + wakeLocks.clear(); + } + } + + /** + * Returns a wake lock to use for {@code key}. If a lock is already present in the map, + * returns that lock. Else, creates a new lock, installs it in the map, and returns it. + * <p> + * REQUIRES: caller must hold {@link #LOCK}. + */ + private PowerManager.WakeLock getWakeLock(Object key) { + if (key == null) { + throw new IllegalArgumentException("Key can not be null"); + } + PowerManager.WakeLock wakeLock = wakeLocks.get(key); + if (wakeLock == null) { + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, key.toString()); + wakeLocks.put(key, wakeLock); + } + return wakeLock; + } + + /** + * Removes any non-held wake locks from {@link #wakeLocks}. Such locks may be present when a + * wake lock acquired with a timeout is not released before the timeout expires. We only + * explicitly remove wake locks from the map when {@link #release} is called, so a timeout results + * in a non-held wake lock in the map. + * <p> + * Must be called as the first line of all non-private methods. + * <p> + * REQUIRES: caller must hold {@link #LOCK}. + */ + private void cleanup() { + Iterator<Map.Entry<Object, WakeLock>> wakeLockIter = wakeLocks.entrySet().iterator(); + + // Check each map entry. + while (wakeLockIter.hasNext()) { + Map.Entry<Object, WakeLock> wakeLockEntry = wakeLockIter.next(); + if (!wakeLockEntry.getValue().isHeld()) { + // Warn and remove the entry from the map if the lock is not held. + logger.warning("Found un-held wakelock '%s' -- timed-out?", wakeLockEntry.getKey()); + wakeLockIter.remove(); + } + } + } + + /** Logs a debug message that {@code action} has occurred for {@code key}. */ + private static void log(Object key, String action) { + logger.fine("WakeLock %s for key: {%s}", action, key); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidChannelConstants.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidChannelConstants.java new file mode 100644 index 0000000..bc0f4c2 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidChannelConstants.java @@ -0,0 +1,128 @@ +/* + * 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.channel; + +import com.google.ipc.invalidation.ticl.proto.AndroidChannel.MajorVersion; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version; + +/** + * Constants used by the network channel. + * + */ +public final class AndroidChannelConstants { + + /** Constants used in Intents sent to retrieve auth tokens from the application. */ + public static class AuthTokenConstants { + /** + * Action requesting that an auth token to send a message be provided. This is the action + * used in the intent to the application. + */ + public static final String ACTION_REQUEST_AUTH_TOKEN = + "com.google.ipc.invalidation.AUTH_TOKEN_REQUEST"; + + /** Extra in an auth token request response providing the pending intent. */ + public static final String EXTRA_PENDING_INTENT = + "com.google.ipc.invalidation.AUTH_TOKEN_PENDING_INTENT"; + + /** + * Extra in an auth token request message indicating that the token provided as the value + * was invalid when last used. This may be set on the intent to the application. + */ + public static final String EXTRA_INVALIDATE_AUTH_TOKEN = + "com.google.ipc.invalidaton.AUTH_TOKEN_INVALIDATE"; + + /** Extra in the intent from the application that provides the auth token string. */ + public static final String EXTRA_AUTH_TOKEN = "com.google.ipc.invalidation.AUTH_TOKEN"; + + /** + * Extra in the intent from the application that provides the so-called "auth token type". If + * the auth token is a GoogleLogin token, then this value must name the Gaia service (e.g., + * "chromiumsync") for which the token was generated. If the auth token is a Gaia OAuth2 token, + * then this value must have the form "oauth2:{scope}", where {scope} is a Google API + * authentication scope such as "https://www.googleapis.com/auth/chromesync". + */ + public static final String EXTRA_AUTH_TOKEN_TYPE = + "com.google.ipc.invalidation.AUTH_TOKEN_TYPE"; + + /** + * Extra in the intent from the application that provides the message to send. We store this + * ourselves in the intent inside the pending intent that we give to the application. + */ + static final String EXTRA_STORED_MESSAGE = "com.google.ipc.invalidation.AUTH_TOKEN_MSG"; + + /** + * Extra in the intent from the application that indicates whether the intent is for a retry + * after a failed authentication. If we find that an auth token no longer works, we will tell + * the application to invalidate it, retrieve a new one, and send us back the message and the + * new token, but we do not want to go into an infinite loop if authentication never succeeds. + */ + static final String EXTRA_IS_RETRY = "com.google.ipc.invalidation.AUTH_TOKEN_IS_RETRY"; + } + + /** Constants used in HTTP requests to the data center. */ + public static class HttpConstants { + /** The URL of the invalidation channel service */ + public static final String CHANNEL_URL = "https://clients4.google.com/"; + + /** The MIME content type to use for requests that contain binary protobuf */ + public static final String PROTO_CONTENT_TYPE = "application/x-protobuffer"; + + /** The relative URL to use to send inbound client requests to the Android frontend */ + public static final String REQUEST_URL = "/invalidation/android/request/"; + + /** + * The name of the query parameter that contains the service name that should be used to + * validate the authentication token provided with the request. + */ + public static final String SERVICE_PARAMETER = "service"; + + /** + * The name of the header that contains the echoed token. This token is included in all C2DM + * messages to the client and is echoed back under this header on all client HTTP requests. + */ + public static final String ECHO_HEADER = "echo-token"; + } + + /** Constants used in C2DM messages. */ + public static class C2dmConstants { + /** + * Name of C2DM parameter containing message content. If not set, data is retrieved via + * the mailbox frontend + */ + public static final String CONTENT_PARAM = "content"; + + /** Name of the C2DM parameter containing an opaque token to be echoed on HTTP requests. */ + public static final String ECHO_PARAM = "echo-token"; + } + + /** The channel version expected by this channel implementation. */ + public static final Version CHANNEL_VERSION = Version.create(MajorVersion.INITIAL, 0); + + /** + * An extra set on an intent to the AndroidMessageSenderService to inform it that a GCM + * registration id change has occurred. This is sent by the AndroidMessageReceiverService + * to trigger the sender service to send any buffered messages when a GCM registration id first + * becomes available. + * <p> + * The value associated with this extra is ignored. + */ + static final String MESSAGE_SENDER_SVC_GCM_REGID_CHANGE = + "com.google.ipc.invalidation.channel.sender.gcm_regid_change"; + + private AndroidChannelConstants() { + // Disallow instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidChannelPreferences.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidChannelPreferences.java new file mode 100644 index 0000000..1dc4c92 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidChannelPreferences.java @@ -0,0 +1,106 @@ +/* + * 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.channel; + +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.ticl.android2.channel.AndroidChannelConstants.C2dmConstants; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Base64; + + +/** Accessor class for shared preference entries used by the channel. */ + public class AndroidChannelPreferences { + /** Name of the preferences in which channel preferences are stored. */ + private static final String PREFERENCES_NAME = "com.google.ipc.invalidation.gcmchannel"; + + /** + * Preferences entry used to buffer the last message sent by the Ticl in the case where a GCM + * registration id is not currently available. + */ + private static final String BUFFERED_MSG_PREF = "buffered-msg"; + + private static final Logger logger = AndroidLogger.forTag("ChannelPrefs"); + + /** Sets the token echoed on subsequent HTTP requests. */ + static void setEchoToken(Context context, String token) { + SharedPreferences.Editor editor = getPreferences(context).edit(); + + // This might fail, but at worst it just means we lose an echo token; the channel + // needs to be able to handle that anyway since it can never assume an echo token + // makes it to the client (since the channel can drop messages). + editor.putString(C2dmConstants.ECHO_PARAM, token); + if (!editor.commit()) { + logger.warning("Failed writing shared preferences for: setEchoToken"); + } + } + + /** Returns the echo token that should be included on HTTP requests. */ + + public static String getEchoToken(Context context) { + return getPreferences(context).getString(C2dmConstants.ECHO_PARAM, null); + } + + /** Buffers the last message sent by the Ticl. Overwrites any previously buffered message. */ + static void bufferMessage(Context context, byte[] message) { + SharedPreferences.Editor editor = getPreferences(context).edit(); + String encodedMessage = + Base64.encodeToString(message, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING); + editor.putString(BUFFERED_MSG_PREF, encodedMessage); + + // This might fail, but at worst we'll just drop a message, which the Ticl must be prepared to + // handle. + if (!editor.commit()) { + logger.warning("Failed writing shared preferences for: bufferMessage"); + } + } + + /** + * Removes and returns the buffered Ticl message, if any. If no message was buffered, returns + * {@code null}. + */ + static byte[] takeBufferedMessage(Context context) { + SharedPreferences preferences = getPreferences(context); + String message = preferences.getString(BUFFERED_MSG_PREF, null); + if (message == null) { + // No message was buffered. + return null; + } + // There is a message to return. Remove the stored value from the preferences. + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(BUFFERED_MSG_PREF); + + // If this fails, we might send the same message twice, which is fine. + if (!editor.commit()) { + logger.warning("Failed writing shared preferences for: takeBufferedMessage"); + } + + // Return the decoded message. + return Base64.decode(message, Base64.URL_SAFE); + } + + /** Returns whether a message has been buffered, for tests. */ + public static boolean hasBufferedMessageForTest(Context context) { + return getPreferences(context).contains(BUFFERED_MSG_PREF); + } + + /** Returns a new {@link SharedPreferences} instance to access the channel preferences. */ + private static SharedPreferences getPreferences(Context context) { + return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidMessageReceiverService.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidMessageReceiverService.java new file mode 100644 index 0000000..54d0fb9 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidMessageReceiverService.java @@ -0,0 +1,135 @@ +/* + * 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.channel; + +import com.google.android.gcm.GCMRegistrar; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener; +import com.google.ipc.invalidation.ticl.android2.AndroidTiclManifest; +import com.google.ipc.invalidation.ticl.android2.ProtocolIntents; +import com.google.ipc.invalidation.ticl.android2.channel.AndroidChannelConstants.C2dmConstants; +import com.google.ipc.invalidation.ticl.proto.AndroidChannel.AddressedAndroidMessage; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; + +import android.content.Context; +import android.content.Intent; +import android.util.Base64; + + +/** + * Service that receives messages from using GCM. + * + */ +public class AndroidMessageReceiverService extends MultiplexingGcmListener.AbstractListener { + /* + * This class is public so that it can be instantiated by the Android runtime. All of the + * {@code onYYY} methods are called holding a wakelock that will be automatically released when + * they return, since this is a subclass of {@code AbstractListener}. + */ + + /** + * Receiver for broadcasts by the multiplexed GCM service. It forwards them to + * AndroidMessageReceiverService. + */ + public static class Receiver extends MultiplexingGcmListener.AbstractListener.Receiver { + /* This class is public so that it can be instantiated by the Android runtime. */ + @Override + protected Class<?> getServiceClass() { + return AndroidMessageReceiverService.class; + } + } + + private final Logger logger = AndroidLogger.forTag("MsgRcvrSvc"); + + public AndroidMessageReceiverService() { + super("AndroidMessageReceiverService"); + } + + @Override + protected void onMessage(Intent intent) { + // Forward the message to the Ticl service. + if (intent.hasExtra(C2dmConstants.CONTENT_PARAM)) { + String content = intent.getStringExtra(C2dmConstants.CONTENT_PARAM); + byte[] msgBytes = Base64.decode(content, Base64.URL_SAFE); + try { + // Look up the name of the Ticl service class from the manifest. + String serviceClass = new AndroidTiclManifest(this).getTiclServiceClass(); + AddressedAndroidMessage addrMessage = AddressedAndroidMessage.parseFrom(msgBytes); + Intent msgIntent = + ProtocolIntents.InternalDowncalls.newServerMessageIntent(addrMessage.getMessage()); + msgIntent.setClassName(this, serviceClass); + startService(msgIntent); + } catch (ValidationException exception) { + logger.warning("Failed parsing inbound message: %s", exception); + } + } else { + logger.fine("GCM Intent has no message content: %s", intent); + } + + // Store the echo token. + String echoToken = intent.getStringExtra(C2dmConstants.ECHO_PARAM); + if (echoToken != null) { + AndroidChannelPreferences.setEchoToken(this, echoToken); + } + } + + @Override + protected void onRegistered(String registrationId) { + // Inform the sender service that the registration id has changed. If the sender service + // had buffered a message because no registration id was previously available, this intent + // will cause it to send that message. + Intent sendBuffered = new Intent(); + final String ignoredData = ""; + sendBuffered.putExtra(AndroidChannelConstants.MESSAGE_SENDER_SVC_GCM_REGID_CHANGE, ignoredData); + sendBuffered.setClass(this, AndroidMessageSenderService.class); + startService(sendBuffered); + + // Inform the Ticl service that the registration id has changed. This will cause it to send + // a message to the data center and update the GCM registration id stored at the data center. + Intent updateServer = ProtocolIntents.InternalDowncalls.newNetworkAddrChangeIntent(); + updateServer.setClassName(this, new AndroidTiclManifest(this).getTiclServiceClass()); + startService(updateServer); + } + + @Override + protected void onUnregistered(String registrationId) { + // Nothing to do. + } + + @Override + protected void onDeletedMessages(int total) { + // This method must be implemented if we start using non-collapsable messages with GCM. For + // now, there is nothing to do. + } + + /** + * Initializes GCM as a convenience method for tests. In production, applications should handle + * this. + */ + public static void initializeGcmForTest(Context context, Logger logger, String senderId) { + // Initialize GCM. + GCMRegistrar.checkDevice(context); + GCMRegistrar.checkManifest(context); + String regId = GCMRegistrar.getRegistrationId(context); + if (regId.isEmpty()) { + logger.info("Not registered with GCM; registering"); + GCMRegistrar.register(context, senderId); + } else { + logger.fine("Already registered with GCM: %s", regId); + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidMessageSenderService.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidMessageSenderService.java new file mode 100644 index 0000000..3e7c3e7 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidMessageSenderService.java @@ -0,0 +1,400 @@ +/* + * 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.channel; + +import com.google.android.gcm.GCMRegistrar; +import com.google.ipc.invalidation.external.client.SystemResources.Logger; +import com.google.ipc.invalidation.external.client.android.service.AndroidLogger; +import com.google.ipc.invalidation.ticl.android2.AndroidTiclManifest; +import com.google.ipc.invalidation.ticl.android2.ProtocolIntents; +import com.google.ipc.invalidation.ticl.android2.channel.AndroidChannelConstants.AuthTokenConstants; +import com.google.ipc.invalidation.ticl.android2.channel.AndroidChannelConstants.HttpConstants; +import com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidNetworkSendRequest; +import com.google.ipc.invalidation.ticl.proto.ChannelCommon.NetworkEndpointId; +import com.google.ipc.invalidation.ticl.proto.CommonProtos; +import com.google.ipc.invalidation.util.Preconditions; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; + +import android.app.IntentService; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.util.Base64; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URL; +import java.util.Arrays; + + +/** + * Service that sends messages to the data center using HTTP POSTs authenticated as a Google + * account. + * <p> + * Messages are sent as byte-serialized {@code ClientToServerMessage} protocol buffers. + * Additionally, the POST requests echo the latest value of the echo token received on C2DM + * messages from the data center. + * + */ +public class AndroidMessageSenderService extends IntentService { + /* This class is public so that it can be instantiated by the Android runtime. */ + + /** + * A prefix on the "auth token type" that indicates we're using an OAuth2 token to authenticate. + */ + private static final String OAUTH2_TOKEN_TYPE_PREFIX = "oauth2:"; + + /** + * Client key used in network endpoint ids. We only have one client at present, so there is no + * need for a key. + */ + private static final String NO_CLIENT_KEY = ""; + + /** An override of the URL, for testing. */ + private static String channelUrlForTest = null; + + private final Logger logger = AndroidLogger.forTag("MsgSenderSvc"); + + /** The last message sent, for tests. */ + public static byte[] lastTiclMessageForTest = null; + + public AndroidMessageSenderService() { + super("AndroidNetworkService"); + setIntentRedelivery(true); + } + + @Override + public void onCreate() { + super.onCreate(); + + // HTTP connection reuse was buggy pre-Froyo, so disable it on those platforms. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { + System.setProperty("http.keepAlive", "false"); + } + } + + @Override + protected void onHandleIntent(Intent intent) { + if (intent == null) { + return; + } + + if (intent.hasExtra(ProtocolIntents.OUTBOUND_MESSAGE_KEY)) { + // Request from the Ticl service to send a message. + handleOutboundMessage(intent.getByteArrayExtra(ProtocolIntents.OUTBOUND_MESSAGE_KEY)); + } else if (intent.hasExtra(AndroidChannelConstants.AuthTokenConstants.EXTRA_AUTH_TOKEN)) { + // Reply from the app with an auth token and a message to send. + handleAuthTokenResponse(intent); + } else if (intent.hasExtra(AndroidChannelConstants.MESSAGE_SENDER_SVC_GCM_REGID_CHANGE)) { + handleGcmRegIdChange(); + } else { + logger.warning("Ignoring intent: %s", intent); + } + } + + /** + * Handles a request to send a message to the data center. Validates the message and sends + * an intent to the application to obtain an auth token to use on the HTTP request to the + * data center. + */ + private void handleOutboundMessage(byte[] sendRequestBytes) { + // Parse and validate the send request. + final AndroidNetworkSendRequest sendRequest; + try { + sendRequest = AndroidNetworkSendRequest.parseFrom(sendRequestBytes); + } catch (ValidationException exception) { + logger.warning("Invalid AndroidNetworkSendRequest from %s: %s", + sendRequestBytes, exception); + return; + } + + // Request an auth token from the application to use when sending the message. + byte[] message = sendRequest.getMessage().getByteArray(); + requestAuthTokenForMessage(message, null); + } + + /** + * Requests an auth token from the application to use to send {@code message} to the data + * center. + * <p> + * If not {@code null}, {@code invalidAuthToken} is an auth token that was previously + * found to be invalid. The intent sent to the application to request the new token will include + * the invalid token so that the application can invalidate it in the {@code AccountManager}. + */ + private void requestAuthTokenForMessage(byte[] message, String invalidAuthToken) { + /* + * Send an intent requesting an auth token. This intent will contain a pending intent + * that the recipient can use to send back the token (by attaching the token as a string + * extra). That pending intent will also contain the message that we were just asked to send, + * so that it will be echoed back to us with the token. This avoids our having to persist + * the message while waiting for the token. + */ + + // This is the intent that the application will send back to us (the pending intent allows + // it to send the intent). It contains the stored message. We require that it be delivered to + // this class only, as a security check. + Intent tokenResponseIntent = new Intent(this, getClass()); + tokenResponseIntent.putExtra(AuthTokenConstants.EXTRA_STORED_MESSAGE, message); + + // If we have an invalid auth token, set a bit in the intent that the application will send + // back to us. This will let us know that it is a retry; if sending subsequently fails again, + // we will not do any further retries. + tokenResponseIntent.putExtra(AuthTokenConstants.EXTRA_IS_RETRY, invalidAuthToken != null); + + // The pending intent allows the application to send us the tokenResponseIntent. + PendingIntent pendingIntent = PendingIntent.getService( + this, Arrays.hashCode(message), tokenResponseIntent, PendingIntent.FLAG_ONE_SHOT); + + // We send the pending intent as an extra in a normal intent to the application. The + // invalidation listener service must handle AUTH_TOKEN_REQUEST intents. + Intent requestTokenIntent = new Intent(AuthTokenConstants.ACTION_REQUEST_AUTH_TOKEN); + requestTokenIntent.putExtra(AuthTokenConstants.EXTRA_PENDING_INTENT, pendingIntent); + if (invalidAuthToken != null) { + requestTokenIntent.putExtra(AuthTokenConstants.EXTRA_INVALIDATE_AUTH_TOKEN, invalidAuthToken); + } + String simpleListenerClass = + new AndroidTiclManifest(getApplicationContext()).getListenerServiceClass(); + requestTokenIntent.setClassName(getApplicationContext(), simpleListenerClass); + try { + startService(requestTokenIntent); + } catch (SecurityException exception) { + logger.warning("unable to request auth token: %s", exception); + } + } + + /** + * Handles an intent received from the application that contains both a message to send and + * an auth token and type to use when sending it. This is called when the reply to the intent + * sent in {@link #requestAuthTokenForMessage(byte[], String)} is received. + */ + private void handleAuthTokenResponse(Intent intent) { + if (!(intent.hasExtra(AuthTokenConstants.EXTRA_STORED_MESSAGE) + && intent.hasExtra(AuthTokenConstants.EXTRA_AUTH_TOKEN) + && intent.hasExtra(AuthTokenConstants.EXTRA_AUTH_TOKEN_TYPE) + && intent.hasExtra(AuthTokenConstants.EXTRA_IS_RETRY))) { + logger.warning("auth-token-response intent missing fields: %s, %s", + intent, intent.getExtras()); + return; + } + boolean isRetryForInvalidAuthToken = + intent.getBooleanExtra(AuthTokenConstants.EXTRA_IS_RETRY, false); + deliverOutboundMessage( + intent.getByteArrayExtra(AuthTokenConstants.EXTRA_STORED_MESSAGE), + intent.getStringExtra(AuthTokenConstants.EXTRA_AUTH_TOKEN), + intent.getStringExtra(AuthTokenConstants.EXTRA_AUTH_TOKEN_TYPE), + isRetryForInvalidAuthToken); + } + + /** + * Sends {@code outgoingMessage} to the data center as a serialized ClientToServerMessage using an + * HTTP POST. + * <p> + * If the HTTP POST fails due to an authentication failure and this is not a retry for an invalid + * auth token ({@code isRetryForInvalidAuthToken} is {@code false}), then it will call + * {@link #requestAuthTokenForMessage(byte[], String)} with {@code authToken} to invalidate the + * token and retry. + * + * @param authToken the auth token to use in the HTTP POST + * @param authTokenType the type of the auth token + */ + private void deliverOutboundMessage(byte[] outgoingMessage, String authToken, + String authTokenType, boolean isRetryForInvalidAuthToken) { + NetworkEndpointId networkEndpointId = getNetworkEndpointId(this, logger); + if (networkEndpointId == null) { + // No GCM registration; buffer the message to send when we become registered. + logger.info("Buffering message to the data center: no GCM registration id"); + AndroidChannelPreferences.bufferMessage(this, outgoingMessage); + return; + } + logger.fine("Delivering outbound message: %s bytes", outgoingMessage.length); + lastTiclMessageForTest = outgoingMessage; + URL url = null; + HttpURLConnection urlConnection = null; + try { + // Open the connection. + boolean isOAuth2Token = authTokenType.startsWith(OAUTH2_TOKEN_TYPE_PREFIX); + url = buildUrl(isOAuth2Token ? null : authTokenType, networkEndpointId); + urlConnection = createUrlConnectionForPost(this, url, authToken, isOAuth2Token); + + // We are seeing EOFException errors when reusing connections. Request that the connection is + // closed on response to work around this issue. Client-to-server messages are batched and + // infrequent so there isn't much benefit in connection reuse here. + urlConnection.setRequestProperty("Connection", "close"); + urlConnection.setFixedLengthStreamingMode(outgoingMessage.length); + urlConnection.connect(); + + // Write the outgoing message. + urlConnection.getOutputStream().write(outgoingMessage); + + // Consume all of the response. We do not do anything with the response (except log it for + // non-200 response codes), and do not expect any, but certain versions of the Apache HTTP + // library have a bug that causes connections to leak when the response is not fully consumed; + // out of sheer paranoia, we do the same thing here. + String response = readCompleteStream(urlConnection.getInputStream()); + + // Retry authorization failures and log other non-200 response codes. + final int responseCode = urlConnection.getResponseCode(); + switch (responseCode) { + case HttpURLConnection.HTTP_OK: + case HttpURLConnection.HTTP_NO_CONTENT: + break; + case HttpURLConnection.HTTP_UNAUTHORIZED: + if (!isRetryForInvalidAuthToken) { + // If we had an auth failure and this is not a retry of an auth failure, then ask the + // application to invalidate authToken and give us a new one with which to retry. We + // check that this attempt was not a retry to avoid infinite loops if authorization + // always fails. + requestAuthTokenForMessage(outgoingMessage, authToken); + } + break; + default: + logger.warning("Unexpected response code %s for HTTP POST to %s; response = %s", + responseCode, url, response); + } + } catch (MalformedURLException exception) { + logger.warning("Malformed URL: %s", exception); + } catch (IOException exception) { + logger.warning("IOException sending to the data center (%s): %s", url, exception); + } finally { + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + } + + /** + * Handles a change in the GCM registration id by sending the buffered client message (if any) + * to the data center. + */ + private void handleGcmRegIdChange() { + byte[] bufferedMessage = AndroidChannelPreferences.takeBufferedMessage(this); + if (bufferedMessage != null) { + // Rejoin the start of the code path that handles sending outbound messages. + requestAuthTokenForMessage(bufferedMessage, null); + } + } + + /** + * Returns a URL to use to send a message to the data center. + * + * @param gaiaServiceId Gaia service for which the request will be authenticated (when using a + * GoogleLogin token), or {@code null} when using an OAuth2 token. + * @param networkEndpointId network id of the client + */ + private static URL buildUrl(String gaiaServiceId, NetworkEndpointId networkEndpointId) + throws MalformedURLException { + StringBuilder urlBuilder = new StringBuilder(); + + // Build base URL that targets the inbound request service with the encoded network endpoint + // id. + urlBuilder.append((channelUrlForTest != null) ? channelUrlForTest : HttpConstants.CHANNEL_URL); + urlBuilder.append(HttpConstants.REQUEST_URL); + + // TODO: We should be sending a ClientGatewayMessage in the request body + // instead of appending the client's network endpoint id to the request URL. Once we do that, we + // should use a UriBuilder to build up a structured Uri object instead of the brittle string + // concatenation we're doing below. + urlBuilder.append(base64Encode(networkEndpointId.toByteArray())); + + // Add query parameter indicating the service to authenticate against + if (gaiaServiceId != null) { + urlBuilder.append('?'); + urlBuilder.append(HttpConstants.SERVICE_PARAMETER); + urlBuilder.append('='); + urlBuilder.append(gaiaServiceId); + } + return new URL(urlBuilder.toString()); + } + + /** + * Returns an {@link HttpURLConnection} to use to POST a message to the data center. Sets + * the content-type and user-agent headers; also sets the echo token header if we have an + * echo token. + * + * @param context Android context + * @param url URL to which to post + * @param authToken auth token to provide in the request header + * @param isOAuth2Token whether the token is an OAuth2 token (vs. a GoogleLogin token) + */ + + public static HttpURLConnection createUrlConnectionForPost(Context context, URL url, + String authToken, boolean isOAuth2Token) throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + try { + connection.setRequestMethod("POST"); + } catch (ProtocolException exception) { + throw new RuntimeException("Cannot set request method to POST: " + exception); + } + connection.setDoOutput(true); + if (isOAuth2Token) { + connection.setRequestProperty("Authorization", "Bearer " + authToken); + } else { + connection.setRequestProperty("Authorization", "GoogleLogin auth=" + authToken); + } + connection.setRequestProperty("Content-Type", HttpConstants.PROTO_CONTENT_TYPE); + connection.setRequestProperty("User-Agent", + context.getApplicationInfo().className + "(" + Build.VERSION.RELEASE + ")"); + String echoToken = AndroidChannelPreferences.getEchoToken(context); + if (echoToken != null) { + // If we have a token to echo to the server, echo it. + connection.setRequestProperty(HttpConstants.ECHO_HEADER, echoToken); + } + return connection; + } + + /** Reads and all data from {@code in}. */ + private static String readCompleteStream(InputStream in) throws IOException { + StringBuffer buffer = new StringBuffer(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line; + while ((line = reader.readLine()) != null) { + buffer.append(line); + } + return buffer.toString(); + } + + /** Returns a base-64 encoded version of {@code bytes}. */ + private static String base64Encode(byte[] bytes) { + return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING); + } + + /** Returns the network id for this channel, or {@code null} if one cannot be determined. */ + + + public static NetworkEndpointId getNetworkEndpointId(Context context, Logger logger) { + String registrationId = GCMRegistrar.getRegistrationId(context); + if ((registrationId == null) || registrationId.isEmpty()) { + // No registration with GCM; we cannot compute a network id. The GCM documentation says the + // string is never null, but we'll be paranoid. + logger.warning("No GCM registration id; cannot determine our network endpoint id: %s", + registrationId); + return null; + } + return CommonProtos.newAndroidEndpointId(registrationId, NO_CLIENT_KEY, + context.getPackageName(), AndroidChannelConstants.CHANNEL_VERSION); + } + + /** Sets the channel url to {@code url}, for tests. */ + public static void setChannelUrlForTest(String url) { + channelUrlForTest = Preconditions.checkNotNull(url); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidNetworkChannel.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidNetworkChannel.java new file mode 100644 index 0000000..afce69c --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidNetworkChannel.java @@ -0,0 +1,62 @@ +/* + * 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.channel; + +import com.google.ipc.invalidation.external.client.SystemResources; +import com.google.ipc.invalidation.ticl.TestableNetworkChannel; +import com.google.ipc.invalidation.ticl.android2.ProtocolIntents; +import com.google.ipc.invalidation.ticl.android2.ResourcesFactory.AndroidResources; +import com.google.ipc.invalidation.ticl.proto.ChannelCommon.NetworkEndpointId; +import com.google.ipc.invalidation.util.Preconditions; + +import android.content.Context; +import android.content.Intent; + +/** + * A network channel for Android that receives messages by GCM and that sends messages + * using HTTP. + * + */ +public class AndroidNetworkChannel implements TestableNetworkChannel { + private final Context context; + private AndroidResources resources; + + public AndroidNetworkChannel(Context context) { + this.context = Preconditions.checkNotNull(context); + } + + @Override + public void sendMessage(byte[] outgoingMessage) { + Intent intent = ProtocolIntents.newOutboundMessageIntent(outgoingMessage); + intent.setClassName(context, AndroidMessageSenderService.class.getName()); + context.startService(intent); + } + + @Override + public void setListener(NetworkListener listener) { + resources.setNetworkListener(listener); + } + + @Override + public void setSystemResources(SystemResources resources) { + this.resources = (AndroidResources) Preconditions.checkNotNull(resources); + } + + @Override + public NetworkEndpointId getNetworkIdForTest() { + return AndroidMessageSenderService.getNetworkEndpointId(context, resources.getLogger()); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidChannel.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidChannel.java new file mode 100644 index 0000000..a922f0d --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidChannel.java @@ -0,0 +1,354 @@ +/* + * 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. + */ +// Generated by j/c/g/ipc/invalidation/common/proto_wrapper_generator +package com.google.ipc.invalidation.ticl.proto; + +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.protobuf.nano.MessageNano; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +public interface AndroidChannel { + + public static final class AndroidEndpointId extends ProtoWrapper { + public static AndroidEndpointId create(String c2DmRegistrationId, + String clientKey, + String senderId, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version channelVersion, + String packageName) { + return new AndroidEndpointId(c2DmRegistrationId, clientKey, senderId, channelVersion, packageName); + } + + public static final AndroidEndpointId DEFAULT_INSTANCE = new AndroidEndpointId(null, null, null, null, null); + + private final long __hazzerBits; + private final String c2DmRegistrationId; + private final String clientKey; + private final String senderId; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version channelVersion; + private final String packageName; + + private AndroidEndpointId(String c2DmRegistrationId, + String clientKey, + String senderId, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version channelVersion, + String packageName) { + int hazzerBits = 0; + if (c2DmRegistrationId != null) { + hazzerBits |= 0x1; + this.c2DmRegistrationId = c2DmRegistrationId; + } else { + this.c2DmRegistrationId = ""; + } + if (clientKey != null) { + hazzerBits |= 0x2; + this.clientKey = clientKey; + } else { + this.clientKey = ""; + } + if (senderId != null) { + hazzerBits |= 0x4; + this.senderId = senderId; + } else { + this.senderId = ""; + } + this.channelVersion = channelVersion; + if (packageName != null) { + hazzerBits |= 0x8; + this.packageName = packageName; + } else { + this.packageName = ""; + } + this.__hazzerBits = hazzerBits; + } + + public String getC2DmRegistrationId() { return c2DmRegistrationId; } + public boolean hasC2DmRegistrationId() { return (0x1 & __hazzerBits) != 0; } + + public String getClientKey() { return clientKey; } + public boolean hasClientKey() { return (0x2 & __hazzerBits) != 0; } + + public String getSenderId() { return senderId; } + public boolean hasSenderId() { return (0x4 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getNullableChannelVersion() { return channelVersion; } + + public String getPackageName() { return packageName; } + public boolean hasPackageName() { return (0x8 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AndroidEndpointId)) { return false; } + AndroidEndpointId other = (AndroidEndpointId) obj; + return __hazzerBits == other.__hazzerBits + && (!hasC2DmRegistrationId() || equals(c2DmRegistrationId, other.c2DmRegistrationId)) + && (!hasClientKey() || equals(clientKey, other.clientKey)) + && (!hasSenderId() || equals(senderId, other.senderId)) + && equals(channelVersion, other.channelVersion) + && (!hasPackageName() || equals(packageName, other.packageName)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasC2DmRegistrationId()) { + result = result * 31 + c2DmRegistrationId.hashCode(); + } + if (hasClientKey()) { + result = result * 31 + clientKey.hashCode(); + } + if (hasSenderId()) { + result = result * 31 + senderId.hashCode(); + } + if (channelVersion != null) { + result = result * 31 + channelVersion.hashCode(); + } + if (hasPackageName()) { + result = result * 31 + packageName.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AndroidEndpointId:"); + if (hasC2DmRegistrationId()) { + builder.append(" c2dm_registration_id=").append(c2DmRegistrationId); + } + if (hasClientKey()) { + builder.append(" client_key=").append(clientKey); + } + if (hasSenderId()) { + builder.append(" sender_id=").append(senderId); + } + if (channelVersion != null) { + builder.append(" channel_version=").append(channelVersion); + } + if (hasPackageName()) { + builder.append(" package_name=").append(packageName); + } + builder.append('>'); + } + + public static AndroidEndpointId parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidChannel.AndroidEndpointId(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AndroidEndpointId fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidChannel.AndroidEndpointId message) { + if (message == null) { return null; } + return new AndroidEndpointId(message.c2DmRegistrationId, + message.clientKey, + message.senderId, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.channelVersion), + message.packageName); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidChannel.AndroidEndpointId toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidChannel.AndroidEndpointId msg = new com.google.protos.ipc.invalidation.NanoAndroidChannel.AndroidEndpointId(); + msg.c2DmRegistrationId = hasC2DmRegistrationId() ? c2DmRegistrationId : null; + msg.clientKey = hasClientKey() ? clientKey : null; + msg.senderId = hasSenderId() ? senderId : null; + msg.channelVersion = this.channelVersion != null ? channelVersion.toMessageNano() : null; + msg.packageName = hasPackageName() ? packageName : null; + return msg; + } + } + + public static final class AddressedAndroidMessage extends ProtoWrapper { + public static AddressedAndroidMessage create(String clientKey, + Bytes message) { + return new AddressedAndroidMessage(clientKey, message); + } + + public static final AddressedAndroidMessage DEFAULT_INSTANCE = new AddressedAndroidMessage(null, null); + + private final long __hazzerBits; + private final String clientKey; + private final Bytes message; + + private AddressedAndroidMessage(String clientKey, + Bytes message) { + int hazzerBits = 0; + if (clientKey != null) { + hazzerBits |= 0x1; + this.clientKey = clientKey; + } else { + this.clientKey = ""; + } + if (message != null) { + hazzerBits |= 0x2; + this.message = message; + } else { + this.message = Bytes.EMPTY_BYTES; + } + this.__hazzerBits = hazzerBits; + } + + public String getClientKey() { return clientKey; } + public boolean hasClientKey() { return (0x1 & __hazzerBits) != 0; } + + public Bytes getMessage() { return message; } + public boolean hasMessage() { return (0x2 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AddressedAndroidMessage)) { return false; } + AddressedAndroidMessage other = (AddressedAndroidMessage) obj; + return __hazzerBits == other.__hazzerBits + && (!hasClientKey() || equals(clientKey, other.clientKey)) + && (!hasMessage() || equals(message, other.message)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasClientKey()) { + result = result * 31 + clientKey.hashCode(); + } + if (hasMessage()) { + result = result * 31 + message.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AddressedAndroidMessage:"); + if (hasClientKey()) { + builder.append(" client_key=").append(clientKey); + } + if (hasMessage()) { + builder.append(" message=").append(message); + } + builder.append('>'); + } + + public static AddressedAndroidMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AddressedAndroidMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessage message) { + if (message == null) { return null; } + return new AddressedAndroidMessage(message.clientKey, + Bytes.fromByteArray(message.message)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessage msg = new com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessage(); + msg.clientKey = hasClientKey() ? clientKey : null; + msg.message = hasMessage() ? message.getByteArray() : null; + return msg; + } + } + + public static final class AddressedAndroidMessageBatch extends ProtoWrapper { + public static AddressedAndroidMessageBatch create(Collection<com.google.ipc.invalidation.ticl.proto.AndroidChannel.AddressedAndroidMessage> addressedMessage) { + return new AddressedAndroidMessageBatch(addressedMessage); + } + + public static final AddressedAndroidMessageBatch DEFAULT_INSTANCE = new AddressedAndroidMessageBatch(null); + + private final List<com.google.ipc.invalidation.ticl.proto.AndroidChannel.AddressedAndroidMessage> addressedMessage; + + private AddressedAndroidMessageBatch(Collection<com.google.ipc.invalidation.ticl.proto.AndroidChannel.AddressedAndroidMessage> addressedMessage) { + this.addressedMessage = optional("addressed_message", addressedMessage); + } + + public List<com.google.ipc.invalidation.ticl.proto.AndroidChannel.AddressedAndroidMessage> getAddressedMessage() { return addressedMessage; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AddressedAndroidMessageBatch)) { return false; } + AddressedAndroidMessageBatch other = (AddressedAndroidMessageBatch) obj; + return equals(addressedMessage, other.addressedMessage); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + addressedMessage.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AddressedAndroidMessageBatch:"); + builder.append(" addressed_message=[").append(addressedMessage).append(']'); + builder.append('>'); + } + + public static AddressedAndroidMessageBatch parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessageBatch(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AddressedAndroidMessageBatch fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessageBatch message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.AndroidChannel.AddressedAndroidMessage> addressedMessage = new ArrayList<com.google.ipc.invalidation.ticl.proto.AndroidChannel.AddressedAndroidMessage>(message.addressedMessage.length); + for (int i = 0; i < message.addressedMessage.length; i++) { + addressedMessage.add(com.google.ipc.invalidation.ticl.proto.AndroidChannel.AddressedAndroidMessage.fromMessageNano(message.addressedMessage[i])); + } + return new AddressedAndroidMessageBatch(addressedMessage); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessageBatch toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessageBatch msg = new com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessageBatch(); + msg.addressedMessage = new com.google.protos.ipc.invalidation.NanoAndroidChannel.AddressedAndroidMessage[addressedMessage.size()]; + for (int i = 0; i < msg.addressedMessage.length; i++) { + msg.addressedMessage[i] = addressedMessage.get(i).toMessageNano(); + } + return msg; + } + } + public interface MajorVersion { + public static final int INITIAL = 0; + public static final int BATCH = 1; + public static final int DEFAULT = 0; + public static final int MIN_SUPPORTED = 0; + public static final int MAX_SUPPORTED = 1; + } + +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidListenerProtocol.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidListenerProtocol.java new file mode 100644 index 0000000..18443a1 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidListenerProtocol.java @@ -0,0 +1,501 @@ +/* + * 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. + */ +// Generated by j/c/g/ipc/invalidation/common/proto_wrapper_generator +package com.google.ipc.invalidation.ticl.proto; + +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.protobuf.nano.MessageNano; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +public interface AndroidListenerProtocol { + + public static final class AndroidListenerState extends ProtoWrapper { + public static final class RetryRegistrationState extends ProtoWrapper { + public static RetryRegistrationState create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState exponentialBackoffState) { + return new RetryRegistrationState(objectId, exponentialBackoffState); + } + + public static final RetryRegistrationState DEFAULT_INSTANCE = new RetryRegistrationState(null, null); + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId; + private final com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState exponentialBackoffState; + + private RetryRegistrationState(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState exponentialBackoffState) { + int hazzerBits = 0; + this.objectId = objectId; + if (exponentialBackoffState != null) { + hazzerBits |= 0x1; + this.exponentialBackoffState = exponentialBackoffState; + } else { + this.exponentialBackoffState = com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState.DEFAULT_INSTANCE; + } + this.__hazzerBits = hazzerBits; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP getNullableObjectId() { return objectId; } + + public com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState getExponentialBackoffState() { return exponentialBackoffState; } + public boolean hasExponentialBackoffState() { return (0x1 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RetryRegistrationState)) { return false; } + RetryRegistrationState other = (RetryRegistrationState) obj; + return __hazzerBits == other.__hazzerBits + && equals(objectId, other.objectId) + && (!hasExponentialBackoffState() || equals(exponentialBackoffState, other.exponentialBackoffState)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (objectId != null) { + result = result * 31 + objectId.hashCode(); + } + if (hasExponentialBackoffState()) { + result = result * 31 + exponentialBackoffState.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RetryRegistrationState:"); + if (objectId != null) { + builder.append(" object_id=").append(objectId); + } + if (hasExponentialBackoffState()) { + builder.append(" exponential_backoff_state=").append(exponentialBackoffState); + } + builder.append('>'); + } + + public static RetryRegistrationState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState.RetryRegistrationState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RetryRegistrationState fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState.RetryRegistrationState message) { + if (message == null) { return null; } + return new RetryRegistrationState(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.objectId), + com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState.fromMessageNano(message.exponentialBackoffState)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState.RetryRegistrationState toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState.RetryRegistrationState msg = new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState.RetryRegistrationState(); + msg.objectId = this.objectId != null ? objectId.toMessageNano() : null; + msg.exponentialBackoffState = hasExponentialBackoffState() ? exponentialBackoffState.toMessageNano() : null; + return msg; + } + } + public static AndroidListenerState create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registration, + Collection<com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState> retryRegistrationState, + Bytes clientId, + Integer requestCodeSeqNum) { + return new AndroidListenerState(registration, retryRegistrationState, clientId, requestCodeSeqNum); + } + + public static final AndroidListenerState DEFAULT_INSTANCE = new AndroidListenerState(null, null, null, null); + + private final long __hazzerBits; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registration; + private final List<com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState> retryRegistrationState; + private final Bytes clientId; + private final int requestCodeSeqNum; + + private AndroidListenerState(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registration, + Collection<com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState> retryRegistrationState, + Bytes clientId, + Integer requestCodeSeqNum) { + int hazzerBits = 0; + this.registration = optional("registration", registration); + this.retryRegistrationState = optional("retry_registration_state", retryRegistrationState); + if (clientId != null) { + hazzerBits |= 0x1; + this.clientId = clientId; + } else { + this.clientId = Bytes.EMPTY_BYTES; + } + if (requestCodeSeqNum != null) { + hazzerBits |= 0x2; + this.requestCodeSeqNum = requestCodeSeqNum; + } else { + this.requestCodeSeqNum = 0; + } + this.__hazzerBits = hazzerBits; + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> getRegistration() { return registration; } + + public List<com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState> getRetryRegistrationState() { return retryRegistrationState; } + + public Bytes getClientId() { return clientId; } + public boolean hasClientId() { return (0x1 & __hazzerBits) != 0; } + + public int getRequestCodeSeqNum() { return requestCodeSeqNum; } + public boolean hasRequestCodeSeqNum() { return (0x2 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AndroidListenerState)) { return false; } + AndroidListenerState other = (AndroidListenerState) obj; + return __hazzerBits == other.__hazzerBits + && equals(registration, other.registration) + && equals(retryRegistrationState, other.retryRegistrationState) + && (!hasClientId() || equals(clientId, other.clientId)) + && (!hasRequestCodeSeqNum() || requestCodeSeqNum == other.requestCodeSeqNum); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + registration.hashCode(); + result = result * 31 + retryRegistrationState.hashCode(); + if (hasClientId()) { + result = result * 31 + clientId.hashCode(); + } + if (hasRequestCodeSeqNum()) { + result = result * 31 + hash(requestCodeSeqNum); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AndroidListenerState:"); + builder.append(" registration=[").append(registration).append(']'); + builder.append(" retry_registration_state=[").append(retryRegistrationState).append(']'); + if (hasClientId()) { + builder.append(" client_id=").append(clientId); + } + if (hasRequestCodeSeqNum()) { + builder.append(" request_code_seq_num=").append(requestCodeSeqNum); + } + builder.append('>'); + } + + public static AndroidListenerState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AndroidListenerState fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registration = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP>(message.registration.length); + for (int i = 0; i < message.registration.length; i++) { + registration.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.registration[i])); + } + List<com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState> retryRegistrationState = new ArrayList<com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState>(message.retryRegistrationState.length); + for (int i = 0; i < message.retryRegistrationState.length; i++) { + retryRegistrationState.add(com.google.ipc.invalidation.ticl.proto.AndroidListenerProtocol.AndroidListenerState.RetryRegistrationState.fromMessageNano(message.retryRegistrationState[i])); + } + return new AndroidListenerState(registration, + retryRegistrationState, + Bytes.fromByteArray(message.clientId), + message.requestCodeSeqNum); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState msg = new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState(); + msg.registration = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP[registration.size()]; + for (int i = 0; i < msg.registration.length; i++) { + msg.registration[i] = registration.get(i).toMessageNano(); + } + msg.retryRegistrationState = new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.AndroidListenerState.RetryRegistrationState[retryRegistrationState.size()]; + for (int i = 0; i < msg.retryRegistrationState.length; i++) { + msg.retryRegistrationState[i] = retryRegistrationState.get(i).toMessageNano(); + } + msg.clientId = hasClientId() ? clientId.getByteArray() : null; + msg.requestCodeSeqNum = hasRequestCodeSeqNum() ? requestCodeSeqNum : null; + return msg; + } + } + + public static final class RegistrationCommand extends ProtoWrapper { + public static RegistrationCommand create(Boolean isRegister, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> objectId, + Bytes clientId, + Boolean isDelayed) { + return new RegistrationCommand(isRegister, objectId, clientId, isDelayed); + } + + public static final RegistrationCommand DEFAULT_INSTANCE = new RegistrationCommand(null, null, null, null); + + private final long __hazzerBits; + private final boolean isRegister; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> objectId; + private final Bytes clientId; + private final boolean isDelayed; + + private RegistrationCommand(Boolean isRegister, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> objectId, + Bytes clientId, + Boolean isDelayed) { + int hazzerBits = 0; + if (isRegister != null) { + hazzerBits |= 0x1; + this.isRegister = isRegister; + } else { + this.isRegister = false; + } + this.objectId = optional("object_id", objectId); + if (clientId != null) { + hazzerBits |= 0x2; + this.clientId = clientId; + } else { + this.clientId = Bytes.EMPTY_BYTES; + } + if (isDelayed != null) { + hazzerBits |= 0x4; + this.isDelayed = isDelayed; + } else { + this.isDelayed = false; + } + this.__hazzerBits = hazzerBits; + } + + public boolean getIsRegister() { return isRegister; } + public boolean hasIsRegister() { return (0x1 & __hazzerBits) != 0; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> getObjectId() { return objectId; } + + public Bytes getClientId() { return clientId; } + public boolean hasClientId() { return (0x2 & __hazzerBits) != 0; } + + public boolean getIsDelayed() { return isDelayed; } + public boolean hasIsDelayed() { return (0x4 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationCommand)) { return false; } + RegistrationCommand other = (RegistrationCommand) obj; + return __hazzerBits == other.__hazzerBits + && (!hasIsRegister() || isRegister == other.isRegister) + && equals(objectId, other.objectId) + && (!hasClientId() || equals(clientId, other.clientId)) + && (!hasIsDelayed() || isDelayed == other.isDelayed); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasIsRegister()) { + result = result * 31 + hash(isRegister); + } + result = result * 31 + objectId.hashCode(); + if (hasClientId()) { + result = result * 31 + clientId.hashCode(); + } + if (hasIsDelayed()) { + result = result * 31 + hash(isDelayed); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationCommand:"); + if (hasIsRegister()) { + builder.append(" is_register=").append(isRegister); + } + builder.append(" object_id=[").append(objectId).append(']'); + if (hasClientId()) { + builder.append(" client_id=").append(clientId); + } + if (hasIsDelayed()) { + builder.append(" is_delayed=").append(isDelayed); + } + builder.append('>'); + } + + public static RegistrationCommand parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.RegistrationCommand(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationCommand fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.RegistrationCommand message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> objectId = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP>(message.objectId.length); + for (int i = 0; i < message.objectId.length; i++) { + objectId.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.objectId[i])); + } + return new RegistrationCommand(message.isRegister, + objectId, + Bytes.fromByteArray(message.clientId), + message.isDelayed); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.RegistrationCommand toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.RegistrationCommand msg = new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.RegistrationCommand(); + msg.isRegister = hasIsRegister() ? isRegister : null; + msg.objectId = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP[objectId.size()]; + for (int i = 0; i < msg.objectId.length; i++) { + msg.objectId[i] = objectId.get(i).toMessageNano(); + } + msg.clientId = hasClientId() ? clientId.getByteArray() : null; + msg.isDelayed = hasIsDelayed() ? isDelayed : null; + return msg; + } + } + + public static final class StartCommand extends ProtoWrapper { + public static StartCommand create(Integer clientType, + Bytes clientName, + Boolean allowSuppression) { + return new StartCommand(clientType, clientName, allowSuppression); + } + + public static final StartCommand DEFAULT_INSTANCE = new StartCommand(null, null, null); + + private final long __hazzerBits; + private final int clientType; + private final Bytes clientName; + private final boolean allowSuppression; + + private StartCommand(Integer clientType, + Bytes clientName, + Boolean allowSuppression) { + int hazzerBits = 0; + if (clientType != null) { + hazzerBits |= 0x1; + this.clientType = clientType; + } else { + this.clientType = 0; + } + if (clientName != null) { + hazzerBits |= 0x2; + this.clientName = clientName; + } else { + this.clientName = Bytes.EMPTY_BYTES; + } + if (allowSuppression != null) { + hazzerBits |= 0x4; + this.allowSuppression = allowSuppression; + } else { + this.allowSuppression = false; + } + this.__hazzerBits = hazzerBits; + } + + public int getClientType() { return clientType; } + public boolean hasClientType() { return (0x1 & __hazzerBits) != 0; } + + public Bytes getClientName() { return clientName; } + public boolean hasClientName() { return (0x2 & __hazzerBits) != 0; } + + public boolean getAllowSuppression() { return allowSuppression; } + public boolean hasAllowSuppression() { return (0x4 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof StartCommand)) { return false; } + StartCommand other = (StartCommand) obj; + return __hazzerBits == other.__hazzerBits + && (!hasClientType() || clientType == other.clientType) + && (!hasClientName() || equals(clientName, other.clientName)) + && (!hasAllowSuppression() || allowSuppression == other.allowSuppression); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasClientType()) { + result = result * 31 + hash(clientType); + } + if (hasClientName()) { + result = result * 31 + clientName.hashCode(); + } + if (hasAllowSuppression()) { + result = result * 31 + hash(allowSuppression); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<StartCommand:"); + if (hasClientType()) { + builder.append(" client_type=").append(clientType); + } + if (hasClientName()) { + builder.append(" client_name=").append(clientName); + } + if (hasAllowSuppression()) { + builder.append(" allow_suppression=").append(allowSuppression); + } + builder.append('>'); + } + + public static StartCommand parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.StartCommand(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static StartCommand fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.StartCommand message) { + if (message == null) { return null; } + return new StartCommand(message.clientType, + Bytes.fromByteArray(message.clientName), + message.allowSuppression); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.StartCommand toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.StartCommand msg = new com.google.protos.ipc.invalidation.NanoAndroidListenerProtocol.StartCommand(); + msg.clientType = hasClientType() ? clientType : null; + msg.clientName = hasClientName() ? clientName.getByteArray() : null; + msg.allowSuppression = hasAllowSuppression() ? allowSuppression : null; + return msg; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidService.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidService.java new file mode 100644 index 0000000..0acdd52 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidService.java @@ -0,0 +1,2009 @@ +/* + * 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. + */ +// Generated by j/c/g/ipc/invalidation/common/proto_wrapper_generator +package com.google.ipc.invalidation.ticl.proto; + +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.protobuf.nano.MessageNano; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +public interface AndroidService { + + public static final class ClientDowncall extends ProtoWrapper { + public static final class StartDowncall extends ProtoWrapper { + public static StartDowncall create() { + return new StartDowncall(); + } + + public static final StartDowncall DEFAULT_INSTANCE = new StartDowncall(); + + + private StartDowncall() { + } + + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof StartDowncall)) { return false; } + StartDowncall other = (StartDowncall) obj; + return true; + } + + @Override protected int computeHashCode() { + int result = 1; + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<StartDowncall:"); + builder.append('>'); + } + + public static StartDowncall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StartDowncall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static StartDowncall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StartDowncall message) { + if (message == null) { return null; } + return new StartDowncall(); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StartDowncall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StartDowncall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StartDowncall(); + return msg; + } + } + public static final class StopDowncall extends ProtoWrapper { + public static StopDowncall create() { + return new StopDowncall(); + } + + public static final StopDowncall DEFAULT_INSTANCE = new StopDowncall(); + + + private StopDowncall() { + } + + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof StopDowncall)) { return false; } + StopDowncall other = (StopDowncall) obj; + return true; + } + + @Override protected int computeHashCode() { + int result = 1; + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<StopDowncall:"); + builder.append('>'); + } + + public static StopDowncall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StopDowncall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static StopDowncall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StopDowncall message) { + if (message == null) { return null; } + return new StopDowncall(); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StopDowncall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StopDowncall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.StopDowncall(); + return msg; + } + } + public static final class AckDowncall extends ProtoWrapper { + public static AckDowncall create(Bytes ackHandle) { + return new AckDowncall(ackHandle); + } + + private final Bytes ackHandle; + + private AckDowncall(Bytes ackHandle) throws ValidationArgumentException { + required("ack_handle", ackHandle); + this.ackHandle = ackHandle; + } + + public Bytes getAckHandle() { return ackHandle; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AckDowncall)) { return false; } + AckDowncall other = (AckDowncall) obj; + return equals(ackHandle, other.ackHandle); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + ackHandle.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AckDowncall:"); + builder.append(" ack_handle=").append(ackHandle); + builder.append('>'); + } + + public static AckDowncall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.AckDowncall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AckDowncall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.AckDowncall message) { + if (message == null) { return null; } + return new AckDowncall(Bytes.fromByteArray(message.ackHandle)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.AckDowncall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.AckDowncall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.AckDowncall(); + msg.ackHandle = ackHandle.getByteArray(); + return msg; + } + } + public static final class RegistrationDowncall extends ProtoWrapper { + public static RegistrationDowncall createWithRegistrations(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registrations) { + return new RegistrationDowncall(registrations, null); + } + + public static RegistrationDowncall createWithUnregistrations(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> unregistrations) { + return new RegistrationDowncall(null, unregistrations); + } + + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registrations; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> unregistrations; + + private RegistrationDowncall(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registrations, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> unregistrations) throws ValidationArgumentException { + this.registrations = optional("registrations", registrations); + this.unregistrations = optional("unregistrations", unregistrations); + String existingOneOfField = null; + if (!this.registrations.isEmpty()) { + existingOneOfField = "registrations"; + } + if (!this.unregistrations.isEmpty()) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "unregistrations"); + } + existingOneOfField = "unregistrations"; + } + if (existingOneOfField == null) { oneOfViolation(); } + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> getRegistrations() { return registrations; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> getUnregistrations() { return unregistrations; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationDowncall)) { return false; } + RegistrationDowncall other = (RegistrationDowncall) obj; + return equals(registrations, other.registrations) + && equals(unregistrations, other.unregistrations); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + registrations.hashCode(); + result = result * 31 + unregistrations.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationDowncall:"); + builder.append(" registrations=[").append(registrations).append(']'); + builder.append(" unregistrations=[").append(unregistrations).append(']'); + builder.append('>'); + } + + public static RegistrationDowncall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.RegistrationDowncall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationDowncall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.RegistrationDowncall message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registrations = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP>(message.registrations.length); + for (int i = 0; i < message.registrations.length; i++) { + registrations.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.registrations[i])); + } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> unregistrations = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP>(message.unregistrations.length); + for (int i = 0; i < message.unregistrations.length; i++) { + unregistrations.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.unregistrations[i])); + } + return new RegistrationDowncall(registrations, + unregistrations); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.RegistrationDowncall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.RegistrationDowncall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall.RegistrationDowncall(); + msg.registrations = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP[registrations.size()]; + for (int i = 0; i < msg.registrations.length; i++) { + msg.registrations[i] = registrations.get(i).toMessageNano(); + } + msg.unregistrations = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP[unregistrations.size()]; + for (int i = 0; i < msg.unregistrations.length; i++) { + msg.unregistrations[i] = unregistrations.get(i).toMessageNano(); + } + return msg; + } + } + public static ClientDowncall createWithSerial(long serial, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version) { + return new ClientDowncall(serial, version, null, null, null, null); + } + + public static ClientDowncall createWithStart(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StartDowncall start) { + return new ClientDowncall(null, version, start, null, null, null); + } + + public static ClientDowncall createWithStop(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StopDowncall stop) { + return new ClientDowncall(null, version, null, stop, null, null); + } + + public static ClientDowncall createWithAck(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.AckDowncall ack) { + return new ClientDowncall(null, version, null, null, ack, null); + } + + public static ClientDowncall createWithRegistrations(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall registrations) { + return new ClientDowncall(null, version, null, null, null, registrations); + } + + private final long __hazzerBits; + private final long serial; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StartDowncall start; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StopDowncall stop; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.AckDowncall ack; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall registrations; + + private ClientDowncall(Long serial, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StartDowncall start, + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StopDowncall stop, + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.AckDowncall ack, + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall registrations) throws ValidationArgumentException { + int hazzerBits = 0; + if (serial != null) { + hazzerBits |= 0x1; + this.serial = serial; + } else { + this.serial = 0; + } + required("version", version); + this.version = version; + if (start != null) { + hazzerBits |= 0x2; + this.start = start; + } else { + this.start = com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StartDowncall.DEFAULT_INSTANCE; + } + if (stop != null) { + hazzerBits |= 0x4; + this.stop = stop; + } else { + this.stop = com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StopDowncall.DEFAULT_INSTANCE; + } + this.ack = ack; + this.registrations = registrations; + this.__hazzerBits = hazzerBits; + String existingOneOfField = null; + if (hasStop()) { + existingOneOfField = "stop"; + } + if (hasStart()) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "start"); + } + existingOneOfField = "start"; + } + if (hasSerial()) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "serial"); + } + existingOneOfField = "serial"; + } + if (this.registrations != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "registrations"); + } + existingOneOfField = "registrations"; + } + if (this.ack != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "ack"); + } + existingOneOfField = "ack"; + } + if (existingOneOfField == null) { oneOfViolation(); } + } + + public long getSerial() { return serial; } + public boolean hasSerial() { return (0x1 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StartDowncall getStart() { return start; } + public boolean hasStart() { return (0x2 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StopDowncall getStop() { return stop; } + public boolean hasStop() { return (0x4 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.AckDowncall getNullableAck() { return ack; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall getNullableRegistrations() { return registrations; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ClientDowncall)) { return false; } + ClientDowncall other = (ClientDowncall) obj; + return __hazzerBits == other.__hazzerBits + && (!hasSerial() || serial == other.serial) + && equals(version, other.version) + && (!hasStart() || equals(start, other.start)) + && (!hasStop() || equals(stop, other.stop)) + && equals(ack, other.ack) + && equals(registrations, other.registrations); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasSerial()) { + result = result * 31 + hash(serial); + } + result = result * 31 + version.hashCode(); + if (hasStart()) { + result = result * 31 + start.hashCode(); + } + if (hasStop()) { + result = result * 31 + stop.hashCode(); + } + if (ack != null) { + result = result * 31 + ack.hashCode(); + } + if (registrations != null) { + result = result * 31 + registrations.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ClientDowncall:"); + if (hasSerial()) { + builder.append(" serial=").append(serial); + } + builder.append(" version=").append(version); + if (hasStart()) { + builder.append(" start=").append(start); + } + if (hasStop()) { + builder.append(" stop=").append(stop); + } + if (ack != null) { + builder.append(" ack=").append(ack); + } + if (registrations != null) { + builder.append(" registrations=").append(registrations); + } + builder.append('>'); + } + + public static ClientDowncall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ClientDowncall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall message) { + if (message == null) { return null; } + return new ClientDowncall(message.serial, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version), + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StartDowncall.fromMessageNano(message.start), + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.StopDowncall.fromMessageNano(message.stop), + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.AckDowncall.fromMessageNano(message.ack), + com.google.ipc.invalidation.ticl.proto.AndroidService.ClientDowncall.RegistrationDowncall.fromMessageNano(message.registrations)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ClientDowncall(); + msg.serial = hasSerial() ? serial : null; + msg.version = version.toMessageNano(); + msg.start = hasStart() ? start.toMessageNano() : null; + msg.stop = hasStop() ? stop.toMessageNano() : null; + msg.ack = this.ack != null ? ack.toMessageNano() : null; + msg.registrations = this.registrations != null ? registrations.toMessageNano() : null; + return msg; + } + } + + public static final class InternalDowncall extends ProtoWrapper { + public static final class ServerMessage extends ProtoWrapper { + public static ServerMessage create(Bytes data) { + return new ServerMessage(data); + } + + private final Bytes data; + + private ServerMessage(Bytes data) throws ValidationArgumentException { + required("data", data); + this.data = data; + } + + public Bytes getData() { return data; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ServerMessage)) { return false; } + ServerMessage other = (ServerMessage) obj; + return equals(data, other.data); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + data.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ServerMessage:"); + builder.append(" data=").append(data); + builder.append('>'); + } + + public static ServerMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.ServerMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ServerMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.ServerMessage message) { + if (message == null) { return null; } + return new ServerMessage(Bytes.fromByteArray(message.data)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.ServerMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.ServerMessage msg = new com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.ServerMessage(); + msg.data = data.getByteArray(); + return msg; + } + } + public static final class NetworkStatus extends ProtoWrapper { + public static NetworkStatus create(boolean isOnline) { + return new NetworkStatus(isOnline); + } + + private final boolean isOnline; + + private NetworkStatus(Boolean isOnline) throws ValidationArgumentException { + required("is_online", isOnline); + this.isOnline = isOnline; + } + + public boolean getIsOnline() { return isOnline; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof NetworkStatus)) { return false; } + NetworkStatus other = (NetworkStatus) obj; + return isOnline == other.isOnline; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(isOnline); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<NetworkStatus:"); + builder.append(" is_online=").append(isOnline); + builder.append('>'); + } + + public static NetworkStatus parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.NetworkStatus(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static NetworkStatus fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.NetworkStatus message) { + if (message == null) { return null; } + return new NetworkStatus(message.isOnline); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.NetworkStatus toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.NetworkStatus msg = new com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.NetworkStatus(); + msg.isOnline = isOnline; + return msg; + } + } + public static final class CreateClient extends ProtoWrapper { + public static CreateClient create(int clientType, + Bytes clientName, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig, + boolean skipStartForTest) { + return new CreateClient(clientType, clientName, clientConfig, skipStartForTest); + } + + private final int clientType; + private final Bytes clientName; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig; + private final boolean skipStartForTest; + + private CreateClient(Integer clientType, + Bytes clientName, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig, + Boolean skipStartForTest) throws ValidationArgumentException { + required("client_type", clientType); + this.clientType = clientType; + required("client_name", clientName); + this.clientName = clientName; + required("client_config", clientConfig); + this.clientConfig = clientConfig; + required("skip_start_for_test", skipStartForTest); + this.skipStartForTest = skipStartForTest; + } + + public int getClientType() { return clientType; } + + public Bytes getClientName() { return clientName; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP getClientConfig() { return clientConfig; } + + public boolean getSkipStartForTest() { return skipStartForTest; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof CreateClient)) { return false; } + CreateClient other = (CreateClient) obj; + return clientType == other.clientType + && equals(clientName, other.clientName) + && equals(clientConfig, other.clientConfig) + && skipStartForTest == other.skipStartForTest; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(clientType); + result = result * 31 + clientName.hashCode(); + result = result * 31 + clientConfig.hashCode(); + result = result * 31 + hash(skipStartForTest); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<CreateClient:"); + builder.append(" client_type=").append(clientType); + builder.append(" client_name=").append(clientName); + builder.append(" client_config=").append(clientConfig); + builder.append(" skip_start_for_test=").append(skipStartForTest); + builder.append('>'); + } + + public static CreateClient parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.CreateClient(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static CreateClient fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.CreateClient message) { + if (message == null) { return null; } + return new CreateClient(message.clientType, + Bytes.fromByteArray(message.clientName), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP.fromMessageNano(message.clientConfig), + message.skipStartForTest); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.CreateClient toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.CreateClient msg = new com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall.CreateClient(); + msg.clientType = clientType; + msg.clientName = clientName.getByteArray(); + msg.clientConfig = clientConfig.toMessageNano(); + msg.skipStartForTest = skipStartForTest; + return msg; + } + } + public static InternalDowncall createWithServerMessage(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.ServerMessage serverMessage) { + return new InternalDowncall(version, serverMessage, null, null, null); + } + + public static InternalDowncall createWithNetworkStatus(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.NetworkStatus networkStatus) { + return new InternalDowncall(version, null, networkStatus, null, null); + } + + public static InternalDowncall createWithNetworkAddrChange(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + boolean networkAddrChange) { + return new InternalDowncall(version, null, null, networkAddrChange, null); + } + + public static InternalDowncall createWithCreateClient(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient createClient) { + return new InternalDowncall(version, null, null, null, createClient); + } + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.ServerMessage serverMessage; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.NetworkStatus networkStatus; + private final boolean networkAddrChange; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient createClient; + + private InternalDowncall(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.ServerMessage serverMessage, + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.NetworkStatus networkStatus, + Boolean networkAddrChange, + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient createClient) throws ValidationArgumentException { + int hazzerBits = 0; + required("version", version); + this.version = version; + this.serverMessage = serverMessage; + this.networkStatus = networkStatus; + if (networkAddrChange != null) { + hazzerBits |= 0x1; + this.networkAddrChange = networkAddrChange; + } else { + this.networkAddrChange = false; + } + this.createClient = createClient; + this.__hazzerBits = hazzerBits; + String existingOneOfField = null; + if (hasNetworkAddrChange()) { + existingOneOfField = "network_addr_change"; + } + if (this.networkStatus != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "network_status"); + } + existingOneOfField = "network_status"; + } + if (this.createClient != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "create_client"); + } + existingOneOfField = "create_client"; + } + if (this.serverMessage != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "server_message"); + } + existingOneOfField = "server_message"; + } + if (existingOneOfField == null) { oneOfViolation(); } + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.ServerMessage getNullableServerMessage() { return serverMessage; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.NetworkStatus getNullableNetworkStatus() { return networkStatus; } + + public boolean getNetworkAddrChange() { return networkAddrChange; } + public boolean hasNetworkAddrChange() { return (0x1 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient getNullableCreateClient() { return createClient; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof InternalDowncall)) { return false; } + InternalDowncall other = (InternalDowncall) obj; + return __hazzerBits == other.__hazzerBits + && equals(version, other.version) + && equals(serverMessage, other.serverMessage) + && equals(networkStatus, other.networkStatus) + && (!hasNetworkAddrChange() || networkAddrChange == other.networkAddrChange) + && equals(createClient, other.createClient); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + version.hashCode(); + if (serverMessage != null) { + result = result * 31 + serverMessage.hashCode(); + } + if (networkStatus != null) { + result = result * 31 + networkStatus.hashCode(); + } + if (hasNetworkAddrChange()) { + result = result * 31 + hash(networkAddrChange); + } + if (createClient != null) { + result = result * 31 + createClient.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<InternalDowncall:"); + builder.append(" version=").append(version); + if (serverMessage != null) { + builder.append(" server_message=").append(serverMessage); + } + if (networkStatus != null) { + builder.append(" network_status=").append(networkStatus); + } + if (hasNetworkAddrChange()) { + builder.append(" network_addr_change=").append(networkAddrChange); + } + if (createClient != null) { + builder.append(" create_client=").append(createClient); + } + builder.append('>'); + } + + public static InternalDowncall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static InternalDowncall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall message) { + if (message == null) { return null; } + return new InternalDowncall(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version), + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.ServerMessage.fromMessageNano(message.serverMessage), + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.NetworkStatus.fromMessageNano(message.networkStatus), + message.networkAddrChange, + com.google.ipc.invalidation.ticl.proto.AndroidService.InternalDowncall.CreateClient.fromMessageNano(message.createClient)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.InternalDowncall(); + msg.version = version.toMessageNano(); + msg.serverMessage = this.serverMessage != null ? serverMessage.toMessageNano() : null; + msg.networkStatus = this.networkStatus != null ? networkStatus.toMessageNano() : null; + msg.networkAddrChange = hasNetworkAddrChange() ? networkAddrChange : null; + msg.createClient = this.createClient != null ? createClient.toMessageNano() : null; + return msg; + } + } + + public static final class ListenerUpcall extends ProtoWrapper { + public static final class ReadyUpcall extends ProtoWrapper { + public static ReadyUpcall create() { + return new ReadyUpcall(); + } + + public static final ReadyUpcall DEFAULT_INSTANCE = new ReadyUpcall(); + + + private ReadyUpcall() { + } + + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ReadyUpcall)) { return false; } + ReadyUpcall other = (ReadyUpcall) obj; + return true; + } + + @Override protected int computeHashCode() { + int result = 1; + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ReadyUpcall:"); + builder.append('>'); + } + + public static ReadyUpcall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReadyUpcall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ReadyUpcall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReadyUpcall message) { + if (message == null) { return null; } + return new ReadyUpcall(); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReadyUpcall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReadyUpcall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReadyUpcall(); + return msg; + } + } + public static final class InvalidateUpcall extends ProtoWrapper { + public static InvalidateUpcall createWithInvalidation(Bytes ackHandle, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP invalidation) { + return new InvalidateUpcall(ackHandle, invalidation, null, null); + } + + public static InvalidateUpcall createWithInvalidateUnknown(Bytes ackHandle, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP invalidateUnknown) { + return new InvalidateUpcall(ackHandle, null, invalidateUnknown, null); + } + + public static InvalidateUpcall createWithInvalidateAll(Bytes ackHandle, + boolean invalidateAll) { + return new InvalidateUpcall(ackHandle, null, null, invalidateAll); + } + + private final long __hazzerBits; + private final Bytes ackHandle; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP invalidation; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP invalidateUnknown; + private final boolean invalidateAll; + + private InvalidateUpcall(Bytes ackHandle, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP invalidation, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP invalidateUnknown, + Boolean invalidateAll) throws ValidationArgumentException { + int hazzerBits = 0; + required("ack_handle", ackHandle); + this.ackHandle = ackHandle; + this.invalidation = invalidation; + this.invalidateUnknown = invalidateUnknown; + if (invalidateAll != null) { + hazzerBits |= 0x1; + this.invalidateAll = invalidateAll; + } else { + this.invalidateAll = false; + } + this.__hazzerBits = hazzerBits; + String existingOneOfField = null; + if (this.invalidateUnknown != null) { + existingOneOfField = "invalidate_unknown"; + } + if (this.invalidation != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "invalidation"); + } + existingOneOfField = "invalidation"; + } + if (hasInvalidateAll()) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "invalidate_all"); + } + existingOneOfField = "invalidate_all"; + } + if (existingOneOfField == null) { oneOfViolation(); } + } + + public Bytes getAckHandle() { return ackHandle; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP getNullableInvalidation() { return invalidation; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP getNullableInvalidateUnknown() { return invalidateUnknown; } + + public boolean getInvalidateAll() { return invalidateAll; } + public boolean hasInvalidateAll() { return (0x1 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof InvalidateUpcall)) { return false; } + InvalidateUpcall other = (InvalidateUpcall) obj; + return __hazzerBits == other.__hazzerBits + && equals(ackHandle, other.ackHandle) + && equals(invalidation, other.invalidation) + && equals(invalidateUnknown, other.invalidateUnknown) + && (!hasInvalidateAll() || invalidateAll == other.invalidateAll); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + ackHandle.hashCode(); + if (invalidation != null) { + result = result * 31 + invalidation.hashCode(); + } + if (invalidateUnknown != null) { + result = result * 31 + invalidateUnknown.hashCode(); + } + if (hasInvalidateAll()) { + result = result * 31 + hash(invalidateAll); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<InvalidateUpcall:"); + builder.append(" ack_handle=").append(ackHandle); + if (invalidation != null) { + builder.append(" invalidation=").append(invalidation); + } + if (invalidateUnknown != null) { + builder.append(" invalidate_unknown=").append(invalidateUnknown); + } + if (hasInvalidateAll()) { + builder.append(" invalidate_all=").append(invalidateAll); + } + builder.append('>'); + } + + public static InvalidateUpcall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.InvalidateUpcall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static InvalidateUpcall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.InvalidateUpcall message) { + if (message == null) { return null; } + return new InvalidateUpcall(Bytes.fromByteArray(message.ackHandle), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP.fromMessageNano(message.invalidation), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.invalidateUnknown), + message.invalidateAll); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.InvalidateUpcall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.InvalidateUpcall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.InvalidateUpcall(); + msg.ackHandle = ackHandle.getByteArray(); + msg.invalidation = this.invalidation != null ? invalidation.toMessageNano() : null; + msg.invalidateUnknown = this.invalidateUnknown != null ? invalidateUnknown.toMessageNano() : null; + msg.invalidateAll = hasInvalidateAll() ? invalidateAll : null; + return msg; + } + } + public static final class RegistrationStatusUpcall extends ProtoWrapper { + public static RegistrationStatusUpcall create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + boolean isRegistered) { + return new RegistrationStatusUpcall(objectId, isRegistered); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId; + private final boolean isRegistered; + + private RegistrationStatusUpcall(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + Boolean isRegistered) throws ValidationArgumentException { + required("object_id", objectId); + this.objectId = objectId; + required("is_registered", isRegistered); + this.isRegistered = isRegistered; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP getObjectId() { return objectId; } + + public boolean getIsRegistered() { return isRegistered; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationStatusUpcall)) { return false; } + RegistrationStatusUpcall other = (RegistrationStatusUpcall) obj; + return equals(objectId, other.objectId) + && isRegistered == other.isRegistered; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + objectId.hashCode(); + result = result * 31 + hash(isRegistered); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationStatusUpcall:"); + builder.append(" object_id=").append(objectId); + builder.append(" is_registered=").append(isRegistered); + builder.append('>'); + } + + public static RegistrationStatusUpcall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationStatusUpcall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationStatusUpcall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationStatusUpcall message) { + if (message == null) { return null; } + return new RegistrationStatusUpcall(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.objectId), + message.isRegistered); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationStatusUpcall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationStatusUpcall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationStatusUpcall(); + msg.objectId = objectId.toMessageNano(); + msg.isRegistered = isRegistered; + return msg; + } + } + public static final class RegistrationFailureUpcall extends ProtoWrapper { + public static RegistrationFailureUpcall create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + boolean transient_, + String message) { + return new RegistrationFailureUpcall(objectId, transient_, message); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId; + private final boolean transient_; + private final String message; + + private RegistrationFailureUpcall(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + Boolean transient_, + String message) throws ValidationArgumentException { + required("object_id", objectId); + this.objectId = objectId; + required("transient", transient_); + this.transient_ = transient_; + required("message", message); + this.message = message; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP getObjectId() { return objectId; } + + public boolean getTransient() { return transient_; } + + public String getMessage() { return message; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationFailureUpcall)) { return false; } + RegistrationFailureUpcall other = (RegistrationFailureUpcall) obj; + return equals(objectId, other.objectId) + && transient_ == other.transient_ + && equals(message, other.message); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + objectId.hashCode(); + result = result * 31 + hash(transient_); + result = result * 31 + message.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationFailureUpcall:"); + builder.append(" object_id=").append(objectId); + builder.append(" transient=").append(transient_); + builder.append(" message=").append(message); + builder.append('>'); + } + + public static RegistrationFailureUpcall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationFailureUpcall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationFailureUpcall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationFailureUpcall message) { + if (message == null) { return null; } + return new RegistrationFailureUpcall(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.objectId), + message.transient_, + message.message); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationFailureUpcall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationFailureUpcall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.RegistrationFailureUpcall(); + msg.objectId = objectId.toMessageNano(); + msg.transient_ = transient_; + msg.message = message; + return msg; + } + } + public static final class ReissueRegistrationsUpcall extends ProtoWrapper { + public static ReissueRegistrationsUpcall create(Bytes prefix, + int length) { + return new ReissueRegistrationsUpcall(prefix, length); + } + + private final Bytes prefix; + private final int length; + + private ReissueRegistrationsUpcall(Bytes prefix, + Integer length) throws ValidationArgumentException { + required("prefix", prefix); + this.prefix = prefix; + required("length", length); + this.length = length; + } + + public Bytes getPrefix() { return prefix; } + + public int getLength() { return length; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ReissueRegistrationsUpcall)) { return false; } + ReissueRegistrationsUpcall other = (ReissueRegistrationsUpcall) obj; + return equals(prefix, other.prefix) + && length == other.length; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + prefix.hashCode(); + result = result * 31 + hash(length); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ReissueRegistrationsUpcall:"); + builder.append(" prefix=").append(prefix); + builder.append(" length=").append(length); + builder.append('>'); + } + + public static ReissueRegistrationsUpcall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReissueRegistrationsUpcall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ReissueRegistrationsUpcall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReissueRegistrationsUpcall message) { + if (message == null) { return null; } + return new ReissueRegistrationsUpcall(Bytes.fromByteArray(message.prefix), + message.length); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReissueRegistrationsUpcall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReissueRegistrationsUpcall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ReissueRegistrationsUpcall(); + msg.prefix = prefix.getByteArray(); + msg.length = length; + return msg; + } + } + public static final class ErrorUpcall extends ProtoWrapper { + public static ErrorUpcall create(int errorCode, + String errorMessage, + boolean isTransient) { + return new ErrorUpcall(errorCode, errorMessage, isTransient); + } + + private final int errorCode; + private final String errorMessage; + private final boolean isTransient; + + private ErrorUpcall(Integer errorCode, + String errorMessage, + Boolean isTransient) throws ValidationArgumentException { + required("error_code", errorCode); + this.errorCode = errorCode; + required("error_message", errorMessage); + this.errorMessage = errorMessage; + required("is_transient", isTransient); + this.isTransient = isTransient; + } + + public int getErrorCode() { return errorCode; } + + public String getErrorMessage() { return errorMessage; } + + public boolean getIsTransient() { return isTransient; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ErrorUpcall)) { return false; } + ErrorUpcall other = (ErrorUpcall) obj; + return errorCode == other.errorCode + && equals(errorMessage, other.errorMessage) + && isTransient == other.isTransient; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(errorCode); + result = result * 31 + errorMessage.hashCode(); + result = result * 31 + hash(isTransient); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ErrorUpcall:"); + builder.append(" error_code=").append(errorCode); + builder.append(" error_message=").append(errorMessage); + builder.append(" is_transient=").append(isTransient); + builder.append('>'); + } + + public static ErrorUpcall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ErrorUpcall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ErrorUpcall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ErrorUpcall message) { + if (message == null) { return null; } + return new ErrorUpcall(message.errorCode, + message.errorMessage, + message.isTransient); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ErrorUpcall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ErrorUpcall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall.ErrorUpcall(); + msg.errorCode = errorCode; + msg.errorMessage = errorMessage; + msg.isTransient = isTransient; + return msg; + } + } + public static ListenerUpcall createWithSerial(long serial, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version) { + return new ListenerUpcall(serial, version, null, null, null, null, null, null); + } + + public static ListenerUpcall createWithReady(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReadyUpcall ready) { + return new ListenerUpcall(null, version, ready, null, null, null, null, null); + } + + public static ListenerUpcall createWithInvalidate(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.InvalidateUpcall invalidate) { + return new ListenerUpcall(null, version, null, invalidate, null, null, null, null); + } + + public static ListenerUpcall createWithRegistrationStatus(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationStatusUpcall registrationStatus) { + return new ListenerUpcall(null, version, null, null, registrationStatus, null, null, null); + } + + public static ListenerUpcall createWithRegistrationFailure(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationFailureUpcall registrationFailure) { + return new ListenerUpcall(null, version, null, null, null, registrationFailure, null, null); + } + + public static ListenerUpcall createWithReissueRegistrations(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReissueRegistrationsUpcall reissueRegistrations) { + return new ListenerUpcall(null, version, null, null, null, null, reissueRegistrations, null); + } + + public static ListenerUpcall createWithError(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ErrorUpcall error) { + return new ListenerUpcall(null, version, null, null, null, null, null, error); + } + + private final long __hazzerBits; + private final long serial; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReadyUpcall ready; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.InvalidateUpcall invalidate; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationStatusUpcall registrationStatus; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationFailureUpcall registrationFailure; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReissueRegistrationsUpcall reissueRegistrations; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ErrorUpcall error; + + private ListenerUpcall(Long serial, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReadyUpcall ready, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.InvalidateUpcall invalidate, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationStatusUpcall registrationStatus, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationFailureUpcall registrationFailure, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReissueRegistrationsUpcall reissueRegistrations, + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ErrorUpcall error) throws ValidationArgumentException { + int hazzerBits = 0; + if (serial != null) { + hazzerBits |= 0x1; + this.serial = serial; + } else { + this.serial = 0; + } + required("version", version); + this.version = version; + if (ready != null) { + hazzerBits |= 0x2; + this.ready = ready; + } else { + this.ready = com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReadyUpcall.DEFAULT_INSTANCE; + } + this.invalidate = invalidate; + this.registrationStatus = registrationStatus; + this.registrationFailure = registrationFailure; + this.reissueRegistrations = reissueRegistrations; + this.error = error; + this.__hazzerBits = hazzerBits; + String existingOneOfField = null; + if (this.reissueRegistrations != null) { + existingOneOfField = "reissue_registrations"; + } + if (hasSerial()) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "serial"); + } + existingOneOfField = "serial"; + } + if (this.registrationFailure != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "registration_failure"); + } + existingOneOfField = "registration_failure"; + } + if (hasReady()) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "ready"); + } + existingOneOfField = "ready"; + } + if (this.invalidate != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "invalidate"); + } + existingOneOfField = "invalidate"; + } + if (this.registrationStatus != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "registration_status"); + } + existingOneOfField = "registration_status"; + } + if (this.error != null) { + if (existingOneOfField != null) { + oneOfViolation(existingOneOfField, "error"); + } + existingOneOfField = "error"; + } + if (existingOneOfField == null) { oneOfViolation(); } + } + + public long getSerial() { return serial; } + public boolean hasSerial() { return (0x1 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReadyUpcall getReady() { return ready; } + public boolean hasReady() { return (0x2 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.InvalidateUpcall getNullableInvalidate() { return invalidate; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationStatusUpcall getNullableRegistrationStatus() { return registrationStatus; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationFailureUpcall getNullableRegistrationFailure() { return registrationFailure; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReissueRegistrationsUpcall getNullableReissueRegistrations() { return reissueRegistrations; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ErrorUpcall getNullableError() { return error; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ListenerUpcall)) { return false; } + ListenerUpcall other = (ListenerUpcall) obj; + return __hazzerBits == other.__hazzerBits + && (!hasSerial() || serial == other.serial) + && equals(version, other.version) + && (!hasReady() || equals(ready, other.ready)) + && equals(invalidate, other.invalidate) + && equals(registrationStatus, other.registrationStatus) + && equals(registrationFailure, other.registrationFailure) + && equals(reissueRegistrations, other.reissueRegistrations) + && equals(error, other.error); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasSerial()) { + result = result * 31 + hash(serial); + } + result = result * 31 + version.hashCode(); + if (hasReady()) { + result = result * 31 + ready.hashCode(); + } + if (invalidate != null) { + result = result * 31 + invalidate.hashCode(); + } + if (registrationStatus != null) { + result = result * 31 + registrationStatus.hashCode(); + } + if (registrationFailure != null) { + result = result * 31 + registrationFailure.hashCode(); + } + if (reissueRegistrations != null) { + result = result * 31 + reissueRegistrations.hashCode(); + } + if (error != null) { + result = result * 31 + error.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ListenerUpcall:"); + if (hasSerial()) { + builder.append(" serial=").append(serial); + } + builder.append(" version=").append(version); + if (hasReady()) { + builder.append(" ready=").append(ready); + } + if (invalidate != null) { + builder.append(" invalidate=").append(invalidate); + } + if (registrationStatus != null) { + builder.append(" registration_status=").append(registrationStatus); + } + if (registrationFailure != null) { + builder.append(" registration_failure=").append(registrationFailure); + } + if (reissueRegistrations != null) { + builder.append(" reissue_registrations=").append(reissueRegistrations); + } + if (error != null) { + builder.append(" error=").append(error); + } + builder.append('>'); + } + + public static ListenerUpcall parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ListenerUpcall fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall message) { + if (message == null) { return null; } + return new ListenerUpcall(message.serial, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version), + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReadyUpcall.fromMessageNano(message.ready), + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.InvalidateUpcall.fromMessageNano(message.invalidate), + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationStatusUpcall.fromMessageNano(message.registrationStatus), + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.RegistrationFailureUpcall.fromMessageNano(message.registrationFailure), + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ReissueRegistrationsUpcall.fromMessageNano(message.reissueRegistrations), + com.google.ipc.invalidation.ticl.proto.AndroidService.ListenerUpcall.ErrorUpcall.fromMessageNano(message.error)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall msg = new com.google.protos.ipc.invalidation.NanoAndroidService.ListenerUpcall(); + msg.serial = hasSerial() ? serial : null; + msg.version = version.toMessageNano(); + msg.ready = hasReady() ? ready.toMessageNano() : null; + msg.invalidate = this.invalidate != null ? invalidate.toMessageNano() : null; + msg.registrationStatus = this.registrationStatus != null ? registrationStatus.toMessageNano() : null; + msg.registrationFailure = this.registrationFailure != null ? registrationFailure.toMessageNano() : null; + msg.reissueRegistrations = this.reissueRegistrations != null ? reissueRegistrations.toMessageNano() : null; + msg.error = this.error != null ? error.toMessageNano() : null; + return msg; + } + } + + public static final class AndroidSchedulerEvent extends ProtoWrapper { + public static AndroidSchedulerEvent create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + String eventName, + long ticlId) { + return new AndroidSchedulerEvent(version, eventName, ticlId); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + private final String eventName; + private final long ticlId; + + private AndroidSchedulerEvent(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + String eventName, + Long ticlId) throws ValidationArgumentException { + required("version", version); + this.version = version; + required("event_name", eventName); + this.eventName = eventName; + required("ticl_id", ticlId); + this.ticlId = ticlId; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + public String getEventName() { return eventName; } + + public long getTiclId() { return ticlId; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AndroidSchedulerEvent)) { return false; } + AndroidSchedulerEvent other = (AndroidSchedulerEvent) obj; + return equals(version, other.version) + && equals(eventName, other.eventName) + && ticlId == other.ticlId; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + version.hashCode(); + result = result * 31 + eventName.hashCode(); + result = result * 31 + hash(ticlId); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AndroidSchedulerEvent:"); + builder.append(" version=").append(version); + builder.append(" event_name=").append(eventName); + builder.append(" ticl_id=").append(ticlId); + builder.append('>'); + } + + public static AndroidSchedulerEvent parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidSchedulerEvent(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AndroidSchedulerEvent fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.AndroidSchedulerEvent message) { + if (message == null) { return null; } + return new AndroidSchedulerEvent(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version), + message.eventName, + message.ticlId); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidSchedulerEvent toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidSchedulerEvent msg = new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidSchedulerEvent(); + msg.version = version.toMessageNano(); + msg.eventName = eventName; + msg.ticlId = ticlId; + return msg; + } + } + + public static final class AndroidNetworkSendRequest extends ProtoWrapper { + public static AndroidNetworkSendRequest create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + Bytes message) { + return new AndroidNetworkSendRequest(version, message); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + private final Bytes message; + + private AndroidNetworkSendRequest(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + Bytes message) throws ValidationArgumentException { + required("version", version); + this.version = version; + required("message", message); + this.message = message; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + public Bytes getMessage() { return message; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AndroidNetworkSendRequest)) { return false; } + AndroidNetworkSendRequest other = (AndroidNetworkSendRequest) obj; + return equals(version, other.version) + && equals(message, other.message); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + version.hashCode(); + result = result * 31 + message.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AndroidNetworkSendRequest:"); + builder.append(" version=").append(version); + builder.append(" message=").append(message); + builder.append('>'); + } + + public static AndroidNetworkSendRequest parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidNetworkSendRequest(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AndroidNetworkSendRequest fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.AndroidNetworkSendRequest message) { + if (message == null) { return null; } + return new AndroidNetworkSendRequest(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version), + Bytes.fromByteArray(message.message)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidNetworkSendRequest toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidNetworkSendRequest msg = new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidNetworkSendRequest(); + msg.version = version.toMessageNano(); + msg.message = message.getByteArray(); + return msg; + } + } + + public static final class AndroidTiclState extends ProtoWrapper { + public static final class Metadata extends ProtoWrapper { + public static Metadata create(int clientType, + Bytes clientName, + long ticlId, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig) { + return new Metadata(clientType, clientName, ticlId, clientConfig); + } + + private final int clientType; + private final Bytes clientName; + private final long ticlId; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig; + + private Metadata(Integer clientType, + Bytes clientName, + Long ticlId, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig) throws ValidationArgumentException { + required("client_type", clientType); + this.clientType = clientType; + required("client_name", clientName); + this.clientName = clientName; + required("ticl_id", ticlId); + this.ticlId = ticlId; + required("client_config", clientConfig); + this.clientConfig = clientConfig; + } + + public int getClientType() { return clientType; } + + public Bytes getClientName() { return clientName; } + + public long getTiclId() { return ticlId; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP getClientConfig() { return clientConfig; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof Metadata)) { return false; } + Metadata other = (Metadata) obj; + return clientType == other.clientType + && equals(clientName, other.clientName) + && ticlId == other.ticlId + && equals(clientConfig, other.clientConfig); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(clientType); + result = result * 31 + clientName.hashCode(); + result = result * 31 + hash(ticlId); + result = result * 31 + clientConfig.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<Metadata:"); + builder.append(" client_type=").append(clientType); + builder.append(" client_name=").append(clientName); + builder.append(" ticl_id=").append(ticlId); + builder.append(" client_config=").append(clientConfig); + builder.append('>'); + } + + public static Metadata parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState.Metadata(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static Metadata fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState.Metadata message) { + if (message == null) { return null; } + return new Metadata(message.clientType, + Bytes.fromByteArray(message.clientName), + message.ticlId, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP.fromMessageNano(message.clientConfig)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState.Metadata toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState.Metadata msg = new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState.Metadata(); + msg.clientType = clientType; + msg.clientName = clientName.getByteArray(); + msg.ticlId = ticlId; + msg.clientConfig = clientConfig.toMessageNano(); + return msg; + } + } + public static AndroidTiclState create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.JavaClient.InvalidationClientState ticlState, + com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState.Metadata metadata) { + return new AndroidTiclState(version, ticlState, metadata); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.InvalidationClientState ticlState; + private final com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState.Metadata metadata; + + private AndroidTiclState(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.JavaClient.InvalidationClientState ticlState, + com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState.Metadata metadata) throws ValidationArgumentException { + required("version", version); + this.version = version; + required("ticl_state", ticlState); + this.ticlState = ticlState; + required("metadata", metadata); + this.metadata = metadata; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.InvalidationClientState getTiclState() { return ticlState; } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState.Metadata getMetadata() { return metadata; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AndroidTiclState)) { return false; } + AndroidTiclState other = (AndroidTiclState) obj; + return equals(version, other.version) + && equals(ticlState, other.ticlState) + && equals(metadata, other.metadata); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + version.hashCode(); + result = result * 31 + ticlState.hashCode(); + result = result * 31 + metadata.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AndroidTiclState:"); + builder.append(" version=").append(version); + builder.append(" ticl_state=").append(ticlState); + builder.append(" metadata=").append(metadata); + builder.append('>'); + } + + public static AndroidTiclState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AndroidTiclState fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState message) { + if (message == null) { return null; } + return new AndroidTiclState(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version), + com.google.ipc.invalidation.ticl.proto.JavaClient.InvalidationClientState.fromMessageNano(message.ticlState), + com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState.Metadata.fromMessageNano(message.metadata)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState msg = new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclState(); + msg.version = version.toMessageNano(); + msg.ticlState = ticlState.toMessageNano(); + msg.metadata = metadata.toMessageNano(); + return msg; + } + } + + public static final class AndroidTiclStateWithDigest extends ProtoWrapper { + public static AndroidTiclStateWithDigest create(com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState state, + Bytes digest) { + return new AndroidTiclStateWithDigest(state, digest); + } + + private final com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState state; + private final Bytes digest; + + private AndroidTiclStateWithDigest(com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState state, + Bytes digest) throws ValidationArgumentException { + required("state", state); + this.state = state; + required("digest", digest); + this.digest = digest; + } + + public com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState getState() { return state; } + + public Bytes getDigest() { return digest; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AndroidTiclStateWithDigest)) { return false; } + AndroidTiclStateWithDigest other = (AndroidTiclStateWithDigest) obj; + return equals(state, other.state) + && equals(digest, other.digest); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + state.hashCode(); + result = result * 31 + digest.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AndroidTiclStateWithDigest:"); + builder.append(" state=").append(state); + builder.append(" digest=").append(digest); + builder.append('>'); + } + + public static AndroidTiclStateWithDigest parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclStateWithDigest(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AndroidTiclStateWithDigest fromMessageNano(com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclStateWithDigest message) { + if (message == null) { return null; } + return new AndroidTiclStateWithDigest(com.google.ipc.invalidation.ticl.proto.AndroidService.AndroidTiclState.fromMessageNano(message.state), + Bytes.fromByteArray(message.digest)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclStateWithDigest toMessageNano() { + com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclStateWithDigest msg = new com.google.protos.ipc.invalidation.NanoAndroidService.AndroidTiclStateWithDigest(); + msg.state = state.toMessageNano(); + msg.digest = digest.getByteArray(); + return msg; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ChannelCommon.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ChannelCommon.java new file mode 100644 index 0000000..bed01e6c --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ChannelCommon.java @@ -0,0 +1,216 @@ +/* + * 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. + */ +// Generated by j/c/g/ipc/invalidation/common/proto_wrapper_generator +package com.google.ipc.invalidation.ticl.proto; + +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.protobuf.nano.MessageNano; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +public interface ChannelCommon { + + public static final class ChannelMessageEncoding extends ProtoWrapper { + public interface MessageEncoding { + public static final int PROTOBUF_BINARY_FORMAT = 1; + public static final int PROTOBUF_JSON_FORMAT = 2; + } + + public static ChannelMessageEncoding create() { + return new ChannelMessageEncoding(); + } + + public static final ChannelMessageEncoding DEFAULT_INSTANCE = new ChannelMessageEncoding(); + + + private ChannelMessageEncoding() { + } + + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ChannelMessageEncoding)) { return false; } + ChannelMessageEncoding other = (ChannelMessageEncoding) obj; + return true; + } + + @Override protected int computeHashCode() { + int result = 1; + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ChannelMessageEncoding:"); + builder.append('>'); + } + + public static ChannelMessageEncoding parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoChannelCommon.ChannelMessageEncoding(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ChannelMessageEncoding fromMessageNano(com.google.protos.ipc.invalidation.NanoChannelCommon.ChannelMessageEncoding message) { + if (message == null) { return null; } + return new ChannelMessageEncoding(); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoChannelCommon.ChannelMessageEncoding toMessageNano() { + com.google.protos.ipc.invalidation.NanoChannelCommon.ChannelMessageEncoding msg = new com.google.protos.ipc.invalidation.NanoChannelCommon.ChannelMessageEncoding(); + return msg; + } + } + + public static final class NetworkEndpointId extends ProtoWrapper { + public interface NetworkAddress { + public static final int TEST = 1; + public static final int BUZZ = 111; + public static final int STUBBY = 112; + public static final int ANDROID = 113; + public static final int LCS = 114; + public static final int TIPS_STUBBY = 115; + } + + public static NetworkEndpointId create(Integer networkAddress, + Bytes clientAddress, + Boolean isOffline) { + return new NetworkEndpointId(networkAddress, clientAddress, isOffline); + } + + public static final NetworkEndpointId DEFAULT_INSTANCE = new NetworkEndpointId(null, null, null); + + private final long __hazzerBits; + private final int networkAddress; + private final Bytes clientAddress; + private final boolean isOffline; + + private NetworkEndpointId(Integer networkAddress, + Bytes clientAddress, + Boolean isOffline) { + int hazzerBits = 0; + if (networkAddress != null) { + hazzerBits |= 0x1; + this.networkAddress = networkAddress; + } else { + this.networkAddress = 1; + } + if (clientAddress != null) { + hazzerBits |= 0x2; + this.clientAddress = clientAddress; + } else { + this.clientAddress = Bytes.EMPTY_BYTES; + } + if (isOffline != null) { + hazzerBits |= 0x4; + this.isOffline = isOffline; + } else { + this.isOffline = false; + } + this.__hazzerBits = hazzerBits; + } + + public int getNetworkAddress() { return networkAddress; } + public boolean hasNetworkAddress() { return (0x1 & __hazzerBits) != 0; } + + public Bytes getClientAddress() { return clientAddress; } + public boolean hasClientAddress() { return (0x2 & __hazzerBits) != 0; } + + public boolean getIsOffline() { return isOffline; } + public boolean hasIsOffline() { return (0x4 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof NetworkEndpointId)) { return false; } + NetworkEndpointId other = (NetworkEndpointId) obj; + return __hazzerBits == other.__hazzerBits + && (!hasNetworkAddress() || networkAddress == other.networkAddress) + && (!hasClientAddress() || equals(clientAddress, other.clientAddress)) + && (!hasIsOffline() || isOffline == other.isOffline); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasNetworkAddress()) { + result = result * 31 + hash(networkAddress); + } + if (hasClientAddress()) { + result = result * 31 + clientAddress.hashCode(); + } + if (hasIsOffline()) { + result = result * 31 + hash(isOffline); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<NetworkEndpointId:"); + if (hasNetworkAddress()) { + builder.append(" network_address=").append(networkAddress); + } + if (hasClientAddress()) { + builder.append(" client_address=").append(clientAddress); + } + if (hasIsOffline()) { + builder.append(" is_offline=").append(isOffline); + } + builder.append('>'); + } + + public static NetworkEndpointId parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoChannelCommon.NetworkEndpointId(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static NetworkEndpointId fromMessageNano(com.google.protos.ipc.invalidation.NanoChannelCommon.NetworkEndpointId message) { + if (message == null) { return null; } + return new NetworkEndpointId(message.networkAddress, + Bytes.fromByteArray(message.clientAddress), + message.isOffline); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoChannelCommon.NetworkEndpointId toMessageNano() { + com.google.protos.ipc.invalidation.NanoChannelCommon.NetworkEndpointId msg = new com.google.protos.ipc.invalidation.NanoChannelCommon.NetworkEndpointId(); + msg.networkAddress = hasNetworkAddress() ? networkAddress : null; + msg.clientAddress = hasClientAddress() ? clientAddress.getByteArray() : null; + msg.isOffline = hasIsOffline() ? isOffline : null; + return msg; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/Client.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/Client.java new file mode 100644 index 0000000..c8e2196 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/Client.java @@ -0,0 +1,483 @@ +/* + * 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. + */ +// Generated by j/c/g/ipc/invalidation/common/proto_wrapper_generator +package com.google.ipc.invalidation.ticl.proto; + +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.protobuf.nano.MessageNano; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +public interface Client { + + public static final class AckHandleP extends ProtoWrapper { + public static AckHandleP create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP invalidation) { + return new AckHandleP(invalidation); + } + + public static final AckHandleP DEFAULT_INSTANCE = new AckHandleP(null); + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP invalidation; + + private AckHandleP(com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP invalidation) { + this.invalidation = invalidation; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP getNullableInvalidation() { return invalidation; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof AckHandleP)) { return false; } + AckHandleP other = (AckHandleP) obj; + return equals(invalidation, other.invalidation); + } + + @Override protected int computeHashCode() { + int result = 1; + if (invalidation != null) { + result = result * 31 + invalidation.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<AckHandleP:"); + if (invalidation != null) { + builder.append(" invalidation=").append(invalidation); + } + builder.append('>'); + } + + public static AckHandleP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClient.AckHandleP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static AckHandleP fromMessageNano(com.google.protos.ipc.invalidation.NanoClient.AckHandleP message) { + if (message == null) { return null; } + return new AckHandleP(com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP.fromMessageNano(message.invalidation)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClient.AckHandleP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClient.AckHandleP msg = new com.google.protos.ipc.invalidation.NanoClient.AckHandleP(); + msg.invalidation = this.invalidation != null ? invalidation.toMessageNano() : null; + return msg; + } + } + + public static final class PersistentTiclState extends ProtoWrapper { + public static final class Builder { + public Bytes clientToken; + public Long lastMessageSendTimeMs; + public Builder() { + } + + public PersistentTiclState build() { + return new PersistentTiclState(clientToken, lastMessageSendTimeMs); + } + } + + public static PersistentTiclState create(Bytes clientToken, + Long lastMessageSendTimeMs) { + return new PersistentTiclState(clientToken, lastMessageSendTimeMs); + } + + public static final PersistentTiclState DEFAULT_INSTANCE = new PersistentTiclState(null, null); + + private final long __hazzerBits; + private final Bytes clientToken; + private final long lastMessageSendTimeMs; + + private PersistentTiclState(Bytes clientToken, + Long lastMessageSendTimeMs) { + int hazzerBits = 0; + if (clientToken != null) { + hazzerBits |= 0x1; + this.clientToken = clientToken; + } else { + this.clientToken = Bytes.EMPTY_BYTES; + } + if (lastMessageSendTimeMs != null) { + hazzerBits |= 0x2; + this.lastMessageSendTimeMs = lastMessageSendTimeMs; + } else { + this.lastMessageSendTimeMs = 0; + } + this.__hazzerBits = hazzerBits; + } + + public Bytes getClientToken() { return clientToken; } + public boolean hasClientToken() { return (0x1 & __hazzerBits) != 0; } + + public long getLastMessageSendTimeMs() { return lastMessageSendTimeMs; } + public boolean hasLastMessageSendTimeMs() { return (0x2 & __hazzerBits) != 0; } + + public Builder toBuilder() { + Builder builder = new Builder(); + if (hasClientToken()) { + builder.clientToken = clientToken; + } + if (hasLastMessageSendTimeMs()) { + builder.lastMessageSendTimeMs = lastMessageSendTimeMs; + } + return builder; + } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof PersistentTiclState)) { return false; } + PersistentTiclState other = (PersistentTiclState) obj; + return __hazzerBits == other.__hazzerBits + && (!hasClientToken() || equals(clientToken, other.clientToken)) + && (!hasLastMessageSendTimeMs() || lastMessageSendTimeMs == other.lastMessageSendTimeMs); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasClientToken()) { + result = result * 31 + clientToken.hashCode(); + } + if (hasLastMessageSendTimeMs()) { + result = result * 31 + hash(lastMessageSendTimeMs); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<PersistentTiclState:"); + if (hasClientToken()) { + builder.append(" client_token=").append(clientToken); + } + if (hasLastMessageSendTimeMs()) { + builder.append(" last_message_send_time_ms=").append(lastMessageSendTimeMs); + } + builder.append('>'); + } + + public static PersistentTiclState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClient.PersistentTiclState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static PersistentTiclState fromMessageNano(com.google.protos.ipc.invalidation.NanoClient.PersistentTiclState message) { + if (message == null) { return null; } + return new PersistentTiclState(Bytes.fromByteArray(message.clientToken), + message.lastMessageSendTimeMs); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClient.PersistentTiclState toMessageNano() { + com.google.protos.ipc.invalidation.NanoClient.PersistentTiclState msg = new com.google.protos.ipc.invalidation.NanoClient.PersistentTiclState(); + msg.clientToken = hasClientToken() ? clientToken.getByteArray() : null; + msg.lastMessageSendTimeMs = hasLastMessageSendTimeMs() ? lastMessageSendTimeMs : null; + return msg; + } + } + + public static final class PersistentStateBlob extends ProtoWrapper { + public static PersistentStateBlob create(com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState ticlState, + Bytes authenticationCode) { + return new PersistentStateBlob(ticlState, authenticationCode); + } + + public static final PersistentStateBlob DEFAULT_INSTANCE = new PersistentStateBlob(null, null); + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState ticlState; + private final Bytes authenticationCode; + + private PersistentStateBlob(com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState ticlState, + Bytes authenticationCode) { + int hazzerBits = 0; + if (ticlState != null) { + hazzerBits |= 0x1; + this.ticlState = ticlState; + } else { + this.ticlState = com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState.DEFAULT_INSTANCE; + } + if (authenticationCode != null) { + hazzerBits |= 0x2; + this.authenticationCode = authenticationCode; + } else { + this.authenticationCode = Bytes.EMPTY_BYTES; + } + this.__hazzerBits = hazzerBits; + } + + public com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState getTiclState() { return ticlState; } + public boolean hasTiclState() { return (0x1 & __hazzerBits) != 0; } + + public Bytes getAuthenticationCode() { return authenticationCode; } + public boolean hasAuthenticationCode() { return (0x2 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof PersistentStateBlob)) { return false; } + PersistentStateBlob other = (PersistentStateBlob) obj; + return __hazzerBits == other.__hazzerBits + && (!hasTiclState() || equals(ticlState, other.ticlState)) + && (!hasAuthenticationCode() || equals(authenticationCode, other.authenticationCode)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasTiclState()) { + result = result * 31 + ticlState.hashCode(); + } + if (hasAuthenticationCode()) { + result = result * 31 + authenticationCode.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<PersistentStateBlob:"); + if (hasTiclState()) { + builder.append(" ticl_state=").append(ticlState); + } + if (hasAuthenticationCode()) { + builder.append(" authentication_code=").append(authenticationCode); + } + builder.append('>'); + } + + public static PersistentStateBlob parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClient.PersistentStateBlob(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static PersistentStateBlob fromMessageNano(com.google.protos.ipc.invalidation.NanoClient.PersistentStateBlob message) { + if (message == null) { return null; } + return new PersistentStateBlob(com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState.fromMessageNano(message.ticlState), + Bytes.fromByteArray(message.authenticationCode)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClient.PersistentStateBlob toMessageNano() { + com.google.protos.ipc.invalidation.NanoClient.PersistentStateBlob msg = new com.google.protos.ipc.invalidation.NanoClient.PersistentStateBlob(); + msg.ticlState = hasTiclState() ? ticlState.toMessageNano() : null; + msg.authenticationCode = hasAuthenticationCode() ? authenticationCode.getByteArray() : null; + return msg; + } + } + + public static final class RunStateP extends ProtoWrapper { + public interface State { + public static final int NOT_STARTED = 1; + public static final int STARTED = 2; + public static final int STOPPED = 3; + } + + public static RunStateP create(Integer state) { + return new RunStateP(state); + } + + public static final RunStateP DEFAULT_INSTANCE = new RunStateP(null); + + private final long __hazzerBits; + private final int state; + + private RunStateP(Integer state) { + int hazzerBits = 0; + if (state != null) { + hazzerBits |= 0x1; + this.state = state; + } else { + this.state = 1; + } + this.__hazzerBits = hazzerBits; + } + + public int getState() { return state; } + public boolean hasState() { return (0x1 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RunStateP)) { return false; } + RunStateP other = (RunStateP) obj; + return __hazzerBits == other.__hazzerBits + && (!hasState() || state == other.state); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasState()) { + result = result * 31 + hash(state); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RunStateP:"); + if (hasState()) { + builder.append(" state=").append(state); + } + builder.append('>'); + } + + public static RunStateP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClient.RunStateP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RunStateP fromMessageNano(com.google.protos.ipc.invalidation.NanoClient.RunStateP message) { + if (message == null) { return null; } + return new RunStateP(message.state); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClient.RunStateP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClient.RunStateP msg = new com.google.protos.ipc.invalidation.NanoClient.RunStateP(); + msg.state = hasState() ? state : null; + return msg; + } + } + + public static final class ExponentialBackoffState extends ProtoWrapper { + public static ExponentialBackoffState create(Integer currentMaxDelay, + Boolean inRetryMode) { + return new ExponentialBackoffState(currentMaxDelay, inRetryMode); + } + + public static final ExponentialBackoffState DEFAULT_INSTANCE = new ExponentialBackoffState(null, null); + + private final long __hazzerBits; + private final int currentMaxDelay; + private final boolean inRetryMode; + + private ExponentialBackoffState(Integer currentMaxDelay, + Boolean inRetryMode) { + int hazzerBits = 0; + if (currentMaxDelay != null) { + hazzerBits |= 0x1; + this.currentMaxDelay = currentMaxDelay; + } else { + this.currentMaxDelay = 0; + } + if (inRetryMode != null) { + hazzerBits |= 0x2; + this.inRetryMode = inRetryMode; + } else { + this.inRetryMode = false; + } + this.__hazzerBits = hazzerBits; + } + + public int getCurrentMaxDelay() { return currentMaxDelay; } + public boolean hasCurrentMaxDelay() { return (0x1 & __hazzerBits) != 0; } + + public boolean getInRetryMode() { return inRetryMode; } + public boolean hasInRetryMode() { return (0x2 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ExponentialBackoffState)) { return false; } + ExponentialBackoffState other = (ExponentialBackoffState) obj; + return __hazzerBits == other.__hazzerBits + && (!hasCurrentMaxDelay() || currentMaxDelay == other.currentMaxDelay) + && (!hasInRetryMode() || inRetryMode == other.inRetryMode); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasCurrentMaxDelay()) { + result = result * 31 + hash(currentMaxDelay); + } + if (hasInRetryMode()) { + result = result * 31 + hash(inRetryMode); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ExponentialBackoffState:"); + if (hasCurrentMaxDelay()) { + builder.append(" current_max_delay=").append(currentMaxDelay); + } + if (hasInRetryMode()) { + builder.append(" in_retry_mode=").append(inRetryMode); + } + builder.append('>'); + } + + public static ExponentialBackoffState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClient.ExponentialBackoffState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ExponentialBackoffState fromMessageNano(com.google.protos.ipc.invalidation.NanoClient.ExponentialBackoffState message) { + if (message == null) { return null; } + return new ExponentialBackoffState(message.currentMaxDelay, + message.inRetryMode); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClient.ExponentialBackoffState toMessageNano() { + com.google.protos.ipc.invalidation.NanoClient.ExponentialBackoffState msg = new com.google.protos.ipc.invalidation.NanoClient.ExponentialBackoffState(); + msg.currentMaxDelay = hasCurrentMaxDelay() ? currentMaxDelay : null; + msg.inRetryMode = hasInRetryMode() ? inRetryMode : null; + return msg; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ClientConstants.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ClientConstants.java new file mode 100644 index 0000000..9612e40 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ClientConstants.java @@ -0,0 +1,55 @@ +/* + * 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.proto; + +import com.google.ipc.invalidation.common.BaseCommonInvalidationConstants; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version; +import com.google.ipc.invalidation.util.Bytes; + +/** Various constant protobufs used in version 2 of the Ticl. */ +public class ClientConstants extends BaseCommonInvalidationConstants { + + /** Version of the client currently being used by the client. */ + public static final Version CLIENT_VERSION_VALUE; + + /** Version of the protocol currently being used by the client/server for v2 clients. */ + public static final ProtocolVersion PROTOCOL_VERSION; + + /** Version of the protocol currently being used by the client/server for v1 clients. */ + public static final ProtocolVersion PROTOCOL_VERSION_V1; + + /** The value of ObjectSource.Type from types.proto. Must be kept in sync with that file. */ + public static final int INTERNAL_OBJECT_SOURCE_TYPE; + + /** Object id used to trigger a refresh of all cached objects ("invalidate-all"). */ + public static final ObjectIdP ALL_OBJECT_ID; + + static { + CLIENT_VERSION_VALUE = Version.create(CLIENT_MAJOR_VERSION, CLIENT_MINOR_VERSION); + PROTOCOL_VERSION = + ProtocolVersion.create(Version.create(PROTOCOL_MAJOR_VERSION, PROTOCOL_MINOR_VERSION)); + PROTOCOL_VERSION_V1 = + ProtocolVersion.create(Version.create(2, 0)); + INTERNAL_OBJECT_SOURCE_TYPE = 1; + ALL_OBJECT_ID = ObjectIdP.create(INTERNAL_OBJECT_SOURCE_TYPE, Bytes.EMPTY_BYTES); + } + + // Prevent instantiation. + private ClientConstants() { + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ClientProtocol.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ClientProtocol.java new file mode 100644 index 0000000..501e580 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ClientProtocol.java @@ -0,0 +1,3171 @@ +/* + * 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. + */ +// Generated by j/c/g/ipc/invalidation/common/proto_wrapper_generator +package com.google.ipc.invalidation.ticl.proto; + +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.protobuf.nano.MessageNano; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +public interface ClientProtocol { + + public static final class Version extends ProtoWrapper { + public static Version create(int majorVersion, + int minorVersion) { + return new Version(majorVersion, minorVersion); + } + + private final int majorVersion; + private final int minorVersion; + + private Version(Integer majorVersion, + Integer minorVersion) throws ValidationArgumentException { + required("major_version", majorVersion); + nonNegative("major_version", majorVersion); + this.majorVersion = majorVersion; + required("minor_version", minorVersion); + nonNegative("minor_version", minorVersion); + this.minorVersion = minorVersion; + } + + public int getMajorVersion() { return majorVersion; } + + public int getMinorVersion() { return minorVersion; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof Version)) { return false; } + Version other = (Version) obj; + return majorVersion == other.majorVersion + && minorVersion == other.minorVersion; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(majorVersion); + result = result * 31 + hash(minorVersion); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<Version:"); + builder.append(" major_version=").append(majorVersion); + builder.append(" minor_version=").append(minorVersion); + builder.append('>'); + } + + public static Version parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.Version(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static Version fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.Version message) { + if (message == null) { return null; } + return new Version(message.majorVersion, + message.minorVersion); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.Version toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.Version msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.Version(); + msg.majorVersion = majorVersion; + msg.minorVersion = minorVersion; + return msg; + } + } + + public static final class ProtocolVersion extends ProtoWrapper { + public static ProtocolVersion create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version) { + return new ProtocolVersion(version); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + + private ProtocolVersion(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version) throws ValidationArgumentException { + required("version", version); + this.version = version; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ProtocolVersion)) { return false; } + ProtocolVersion other = (ProtocolVersion) obj; + return equals(version, other.version); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + version.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ProtocolVersion:"); + builder.append(" version=").append(version); + builder.append('>'); + } + + public static ProtocolVersion parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolVersion(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ProtocolVersion fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolVersion message) { + if (message == null) { return null; } + return new ProtocolVersion(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolVersion toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolVersion msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolVersion(); + msg.version = version.toMessageNano(); + return msg; + } + } + + public static final class ClientVersion extends ProtoWrapper { + public static ClientVersion create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + String platform, + String language, + String applicationInfo) { + return new ClientVersion(version, platform, language, applicationInfo); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + private final String platform; + private final String language; + private final String applicationInfo; + + private ClientVersion(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + String platform, + String language, + String applicationInfo) throws ValidationArgumentException { + required("version", version); + this.version = version; + required("platform", platform); + this.platform = platform; + required("language", language); + this.language = language; + required("application_info", applicationInfo); + this.applicationInfo = applicationInfo; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + public String getPlatform() { return platform; } + + public String getLanguage() { return language; } + + public String getApplicationInfo() { return applicationInfo; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ClientVersion)) { return false; } + ClientVersion other = (ClientVersion) obj; + return equals(version, other.version) + && equals(platform, other.platform) + && equals(language, other.language) + && equals(applicationInfo, other.applicationInfo); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + version.hashCode(); + result = result * 31 + platform.hashCode(); + result = result * 31 + language.hashCode(); + result = result * 31 + applicationInfo.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ClientVersion:"); + builder.append(" version=").append(version); + builder.append(" platform=").append(platform); + builder.append(" language=").append(language); + builder.append(" application_info=").append(applicationInfo); + builder.append('>'); + } + + public static ClientVersion parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ClientVersion(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ClientVersion fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ClientVersion message) { + if (message == null) { return null; } + return new ClientVersion(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version), + message.platform, + message.language, + message.applicationInfo); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ClientVersion toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ClientVersion msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ClientVersion(); + msg.version = version.toMessageNano(); + msg.platform = platform; + msg.language = language; + msg.applicationInfo = applicationInfo; + return msg; + } + } + + public static final class StatusP extends ProtoWrapper { + public interface Code { + public static final int SUCCESS = 1; + public static final int TRANSIENT_FAILURE = 2; + public static final int PERMANENT_FAILURE = 3; + } + + public static StatusP create(int code, + String description) { + return new StatusP(code, description); + } + + private final long __hazzerBits; + private final int code; + private final String description; + + private StatusP(Integer code, + String description) throws ValidationArgumentException { + int hazzerBits = 0; + required("code", code); + this.code = code; + if (description != null) { + hazzerBits |= 0x1; + this.description = description; + } else { + this.description = ""; + } + this.__hazzerBits = hazzerBits; + } + + public int getCode() { return code; } + + public String getDescription() { return description; } + public boolean hasDescription() { return (0x1 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof StatusP)) { return false; } + StatusP other = (StatusP) obj; + return __hazzerBits == other.__hazzerBits + && code == other.code + && (!hasDescription() || equals(description, other.description)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + hash(code); + if (hasDescription()) { + result = result * 31 + description.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<StatusP:"); + builder.append(" code=").append(code); + if (hasDescription()) { + builder.append(" description=").append(description); + } + builder.append('>'); + } + + public static StatusP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.StatusP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static StatusP fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.StatusP message) { + if (message == null) { return null; } + return new StatusP(message.code, + message.description); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.StatusP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.StatusP msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.StatusP(); + msg.code = code; + msg.description = hasDescription() ? description : null; + return msg; + } + } + + public static final class ObjectIdP extends ProtoWrapper { + public static ObjectIdP create(int source, + Bytes name) { + return new ObjectIdP(source, name); + } + + private final int source; + private final Bytes name; + + private ObjectIdP(Integer source, + Bytes name) throws ValidationArgumentException { + required("source", source); + nonNegative("source", source); + this.source = source; + required("name", name); + this.name = name; + } + + public int getSource() { return source; } + + public Bytes getName() { return name; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ObjectIdP)) { return false; } + ObjectIdP other = (ObjectIdP) obj; + return source == other.source + && equals(name, other.name); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(source); + result = result * 31 + name.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ObjectIdP:"); + builder.append(" source=").append(source); + builder.append(" name=").append(name); + builder.append('>'); + } + + public static ObjectIdP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ObjectIdP fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP message) { + if (message == null) { return null; } + return new ObjectIdP(message.source, + Bytes.fromByteArray(message.name)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP(); + msg.source = source; + msg.name = name.getByteArray(); + return msg; + } + } + + public static final class ApplicationClientIdP extends ProtoWrapper { + public static ApplicationClientIdP create(Integer clientType, + Bytes clientName) { + return new ApplicationClientIdP(clientType, clientName); + } + + private final long __hazzerBits; + private final int clientType; + private final Bytes clientName; + + private ApplicationClientIdP(Integer clientType, + Bytes clientName) throws ValidationArgumentException { + int hazzerBits = 0; + if (clientType != null) { + hazzerBits |= 0x1; + this.clientType = clientType; + } else { + this.clientType = 0; + } + required("client_name", clientName); + nonEmpty("client_name", clientName); + this.clientName = clientName; + this.__hazzerBits = hazzerBits; + } + + public int getClientType() { return clientType; } + public boolean hasClientType() { return (0x1 & __hazzerBits) != 0; } + + public Bytes getClientName() { return clientName; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ApplicationClientIdP)) { return false; } + ApplicationClientIdP other = (ApplicationClientIdP) obj; + return __hazzerBits == other.__hazzerBits + && (!hasClientType() || clientType == other.clientType) + && equals(clientName, other.clientName); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasClientType()) { + result = result * 31 + hash(clientType); + } + result = result * 31 + clientName.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ApplicationClientIdP:"); + if (hasClientType()) { + builder.append(" client_type=").append(clientType); + } + builder.append(" client_name=").append(clientName); + builder.append('>'); + } + + public static ApplicationClientIdP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ApplicationClientIdP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ApplicationClientIdP fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ApplicationClientIdP message) { + if (message == null) { return null; } + return new ApplicationClientIdP(message.clientType, + Bytes.fromByteArray(message.clientName)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ApplicationClientIdP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ApplicationClientIdP msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ApplicationClientIdP(); + msg.clientType = hasClientType() ? clientType : null; + msg.clientName = clientName.getByteArray(); + return msg; + } + } + + public static final class InvalidationP extends ProtoWrapper { + public static final class Builder { + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId; + public boolean isKnownVersion; + public long version; + public Bytes payload; + public Long bridgeArrivalTimeMsDeprecated; + public Boolean isTrickleRestart; + public Builder(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + boolean isKnownVersion, + long version) { + this.objectId = objectId;this.isKnownVersion = isKnownVersion;this.version = version;} + + public InvalidationP build() { + return new InvalidationP(objectId, isKnownVersion, version, payload, bridgeArrivalTimeMsDeprecated, isTrickleRestart); + } + } + + public static InvalidationP create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + boolean isKnownVersion, + long version, + Bytes payload, + Long bridgeArrivalTimeMsDeprecated, + Boolean isTrickleRestart) { + return new InvalidationP(objectId, isKnownVersion, version, payload, bridgeArrivalTimeMsDeprecated, isTrickleRestart); + } + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId; + private final boolean isKnownVersion; + private final long version; + private final Bytes payload; + private final long bridgeArrivalTimeMsDeprecated; + private final boolean isTrickleRestart; + + private InvalidationP(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + Boolean isKnownVersion, + Long version, + Bytes payload, + Long bridgeArrivalTimeMsDeprecated, + Boolean isTrickleRestart) throws ValidationArgumentException { + int hazzerBits = 0; + required("object_id", objectId); + this.objectId = objectId; + required("is_known_version", isKnownVersion); + this.isKnownVersion = isKnownVersion; + required("version", version); + nonNegative("version", version); + this.version = version; + if (payload != null) { + hazzerBits |= 0x1; + this.payload = payload; + } else { + this.payload = Bytes.EMPTY_BYTES; + } + if (bridgeArrivalTimeMsDeprecated != null) { + hazzerBits |= 0x2; + this.bridgeArrivalTimeMsDeprecated = bridgeArrivalTimeMsDeprecated; + } else { + this.bridgeArrivalTimeMsDeprecated = 0; + } + if (isTrickleRestart != null) { + hazzerBits |= 0x4; + this.isTrickleRestart = isTrickleRestart; + } else { + this.isTrickleRestart = true; + } + this.__hazzerBits = hazzerBits; + check(isKnownVersion || (isTrickleRestart == null || isTrickleRestart), + "is_trickle_restart required if not is_known_version"); + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP getObjectId() { return objectId; } + + public boolean getIsKnownVersion() { return isKnownVersion; } + + public long getVersion() { return version; } + + public Bytes getPayload() { return payload; } + public boolean hasPayload() { return (0x1 & __hazzerBits) != 0; } + + public long getBridgeArrivalTimeMsDeprecated() { return bridgeArrivalTimeMsDeprecated; } + public boolean hasBridgeArrivalTimeMsDeprecated() { return (0x2 & __hazzerBits) != 0; } + + public boolean getIsTrickleRestart() { return isTrickleRestart; } + public boolean hasIsTrickleRestart() { return (0x4 & __hazzerBits) != 0; } + + public Builder toBuilder() { + Builder builder = new Builder(objectId, isKnownVersion, version); + if (hasPayload()) { + builder.payload = payload; + } + if (hasBridgeArrivalTimeMsDeprecated()) { + builder.bridgeArrivalTimeMsDeprecated = bridgeArrivalTimeMsDeprecated; + } + if (hasIsTrickleRestart()) { + builder.isTrickleRestart = isTrickleRestart; + } + return builder; + } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof InvalidationP)) { return false; } + InvalidationP other = (InvalidationP) obj; + return __hazzerBits == other.__hazzerBits + && equals(objectId, other.objectId) + && isKnownVersion == other.isKnownVersion + && version == other.version + && (!hasPayload() || equals(payload, other.payload)) + && (!hasBridgeArrivalTimeMsDeprecated() || bridgeArrivalTimeMsDeprecated == other.bridgeArrivalTimeMsDeprecated) + && (!hasIsTrickleRestart() || isTrickleRestart == other.isTrickleRestart); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + objectId.hashCode(); + result = result * 31 + hash(isKnownVersion); + result = result * 31 + hash(version); + if (hasPayload()) { + result = result * 31 + payload.hashCode(); + } + if (hasBridgeArrivalTimeMsDeprecated()) { + result = result * 31 + hash(bridgeArrivalTimeMsDeprecated); + } + if (hasIsTrickleRestart()) { + result = result * 31 + hash(isTrickleRestart); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<InvalidationP:"); + builder.append(" object_id=").append(objectId); + builder.append(" is_known_version=").append(isKnownVersion); + builder.append(" version=").append(version); + if (hasPayload()) { + builder.append(" payload=").append(payload); + } + if (hasBridgeArrivalTimeMsDeprecated()) { + builder.append(" bridge_arrival_time_ms_deprecated=").append(bridgeArrivalTimeMsDeprecated); + } + if (hasIsTrickleRestart()) { + builder.append(" is_trickle_restart=").append(isTrickleRestart); + } + builder.append('>'); + } + + public static InvalidationP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static InvalidationP fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationP message) { + if (message == null) { return null; } + return new InvalidationP(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.objectId), + message.isKnownVersion, + message.version, + Bytes.fromByteArray(message.payload), + message.bridgeArrivalTimeMsDeprecated, + message.isTrickleRestart); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationP msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationP(); + msg.objectId = objectId.toMessageNano(); + msg.isKnownVersion = isKnownVersion; + msg.version = version; + msg.payload = hasPayload() ? payload.getByteArray() : null; + msg.bridgeArrivalTimeMsDeprecated = hasBridgeArrivalTimeMsDeprecated() ? bridgeArrivalTimeMsDeprecated : null; + msg.isTrickleRestart = hasIsTrickleRestart() ? isTrickleRestart : null; + return msg; + } + } + + public static final class RegistrationP extends ProtoWrapper { + public interface OpType { + public static final int REGISTER = 1; + public static final int UNREGISTER = 2; + } + + public static RegistrationP create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + int opType) { + return new RegistrationP(objectId, opType); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId; + private final int opType; + + private RegistrationP(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP objectId, + Integer opType) throws ValidationArgumentException { + required("object_id", objectId); + this.objectId = objectId; + required("op_type", opType); + this.opType = opType; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP getObjectId() { return objectId; } + + public int getOpType() { return opType; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationP)) { return false; } + RegistrationP other = (RegistrationP) obj; + return equals(objectId, other.objectId) + && opType == other.opType; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + objectId.hashCode(); + result = result * 31 + hash(opType); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationP:"); + builder.append(" object_id=").append(objectId); + builder.append(" op_type=").append(opType); + builder.append('>'); + } + + public static RegistrationP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationP fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationP message) { + if (message == null) { return null; } + return new RegistrationP(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.objectId), + message.opType); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationP msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationP(); + msg.objectId = objectId.toMessageNano(); + msg.opType = opType; + return msg; + } + } + + public static final class RegistrationSummary extends ProtoWrapper { + public static RegistrationSummary create(int numRegistrations, + Bytes registrationDigest) { + return new RegistrationSummary(numRegistrations, registrationDigest); + } + + private final int numRegistrations; + private final Bytes registrationDigest; + + private RegistrationSummary(Integer numRegistrations, + Bytes registrationDigest) throws ValidationArgumentException { + required("num_registrations", numRegistrations); + nonNegative("num_registrations", numRegistrations); + this.numRegistrations = numRegistrations; + required("registration_digest", registrationDigest); + nonEmpty("registration_digest", registrationDigest); + this.registrationDigest = registrationDigest; + } + + public int getNumRegistrations() { return numRegistrations; } + + public Bytes getRegistrationDigest() { return registrationDigest; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationSummary)) { return false; } + RegistrationSummary other = (RegistrationSummary) obj; + return numRegistrations == other.numRegistrations + && equals(registrationDigest, other.registrationDigest); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(numRegistrations); + result = result * 31 + registrationDigest.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationSummary:"); + builder.append(" num_registrations=").append(numRegistrations); + builder.append(" registration_digest=").append(registrationDigest); + builder.append('>'); + } + + public static RegistrationSummary parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSummary(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationSummary fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSummary message) { + if (message == null) { return null; } + return new RegistrationSummary(message.numRegistrations, + Bytes.fromByteArray(message.registrationDigest)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSummary toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSummary msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSummary(); + msg.numRegistrations = numRegistrations; + msg.registrationDigest = registrationDigest.getByteArray(); + return msg; + } + } + + public static final class ClientHeader extends ProtoWrapper { + public static ClientHeader create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion protocolVersion, + Bytes clientToken, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary registrationSummary, + long clientTimeMs, + long maxKnownServerTimeMs, + String messageId, + Integer clientType) { + return new ClientHeader(protocolVersion, clientToken, registrationSummary, clientTimeMs, maxKnownServerTimeMs, messageId, clientType); + } + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion protocolVersion; + private final Bytes clientToken; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary registrationSummary; + private final long clientTimeMs; + private final long maxKnownServerTimeMs; + private final String messageId; + private final int clientType; + + private ClientHeader(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion protocolVersion, + Bytes clientToken, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary registrationSummary, + Long clientTimeMs, + Long maxKnownServerTimeMs, + String messageId, + Integer clientType) throws ValidationArgumentException { + int hazzerBits = 0; + required("protocol_version", protocolVersion); + this.protocolVersion = protocolVersion; + if (clientToken != null) { + hazzerBits |= 0x1; + nonEmpty("client_token", clientToken); + this.clientToken = clientToken; + } else { + this.clientToken = Bytes.EMPTY_BYTES; + } + this.registrationSummary = registrationSummary; + required("client_time_ms", clientTimeMs); + nonNegative("client_time_ms", clientTimeMs); + this.clientTimeMs = clientTimeMs; + required("max_known_server_time_ms", maxKnownServerTimeMs); + nonNegative("max_known_server_time_ms", maxKnownServerTimeMs); + this.maxKnownServerTimeMs = maxKnownServerTimeMs; + if (messageId != null) { + hazzerBits |= 0x2; + nonEmpty("message_id", messageId); + this.messageId = messageId; + } else { + this.messageId = ""; + } + if (clientType != null) { + hazzerBits |= 0x4; + this.clientType = clientType; + } else { + this.clientType = 0; + } + this.__hazzerBits = hazzerBits; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion getProtocolVersion() { return protocolVersion; } + + public Bytes getClientToken() { return clientToken; } + public boolean hasClientToken() { return (0x1 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary getNullableRegistrationSummary() { return registrationSummary; } + + public long getClientTimeMs() { return clientTimeMs; } + + public long getMaxKnownServerTimeMs() { return maxKnownServerTimeMs; } + + public String getMessageId() { return messageId; } + public boolean hasMessageId() { return (0x2 & __hazzerBits) != 0; } + + public int getClientType() { return clientType; } + public boolean hasClientType() { return (0x4 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ClientHeader)) { return false; } + ClientHeader other = (ClientHeader) obj; + return __hazzerBits == other.__hazzerBits + && equals(protocolVersion, other.protocolVersion) + && (!hasClientToken() || equals(clientToken, other.clientToken)) + && equals(registrationSummary, other.registrationSummary) + && clientTimeMs == other.clientTimeMs + && maxKnownServerTimeMs == other.maxKnownServerTimeMs + && (!hasMessageId() || equals(messageId, other.messageId)) + && (!hasClientType() || clientType == other.clientType); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + protocolVersion.hashCode(); + if (hasClientToken()) { + result = result * 31 + clientToken.hashCode(); + } + if (registrationSummary != null) { + result = result * 31 + registrationSummary.hashCode(); + } + result = result * 31 + hash(clientTimeMs); + result = result * 31 + hash(maxKnownServerTimeMs); + if (hasMessageId()) { + result = result * 31 + messageId.hashCode(); + } + if (hasClientType()) { + result = result * 31 + hash(clientType); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ClientHeader:"); + builder.append(" protocol_version=").append(protocolVersion); + if (hasClientToken()) { + builder.append(" client_token=").append(clientToken); + } + if (registrationSummary != null) { + builder.append(" registration_summary=").append(registrationSummary); + } + builder.append(" client_time_ms=").append(clientTimeMs); + builder.append(" max_known_server_time_ms=").append(maxKnownServerTimeMs); + if (hasMessageId()) { + builder.append(" message_id=").append(messageId); + } + if (hasClientType()) { + builder.append(" client_type=").append(clientType); + } + builder.append('>'); + } + + public static ClientHeader parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ClientHeader(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ClientHeader fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ClientHeader message) { + if (message == null) { return null; } + return new ClientHeader(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion.fromMessageNano(message.protocolVersion), + Bytes.fromByteArray(message.clientToken), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary.fromMessageNano(message.registrationSummary), + message.clientTimeMs, + message.maxKnownServerTimeMs, + message.messageId, + message.clientType); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ClientHeader toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ClientHeader msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ClientHeader(); + msg.protocolVersion = protocolVersion.toMessageNano(); + msg.clientToken = hasClientToken() ? clientToken.getByteArray() : null; + msg.registrationSummary = this.registrationSummary != null ? registrationSummary.toMessageNano() : null; + msg.clientTimeMs = clientTimeMs; + msg.maxKnownServerTimeMs = maxKnownServerTimeMs; + msg.messageId = hasMessageId() ? messageId : null; + msg.clientType = hasClientType() ? clientType : null; + return msg; + } + } + + public static final class ClientToServerMessage extends ProtoWrapper { + public static ClientToServerMessage create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientHeader header, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage initializeMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationMessage registrationMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncMessage registrationSyncMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage invalidationAckMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage infoMessage) { + return new ClientToServerMessage(header, initializeMessage, registrationMessage, registrationSyncMessage, invalidationAckMessage, infoMessage); + } + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientHeader header; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage initializeMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationMessage registrationMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncMessage registrationSyncMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage invalidationAckMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage infoMessage; + + private ClientToServerMessage(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientHeader header, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage initializeMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationMessage registrationMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncMessage registrationSyncMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage invalidationAckMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage infoMessage) throws ValidationArgumentException { + int hazzerBits = 0; + required("header", header); + this.header = header; + this.initializeMessage = initializeMessage; + this.registrationMessage = registrationMessage; + this.registrationSyncMessage = registrationSyncMessage; + this.invalidationAckMessage = invalidationAckMessage; + if (infoMessage != null) { + hazzerBits |= 0x1; + this.infoMessage = infoMessage; + } else { + this.infoMessage = com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage.DEFAULT_INSTANCE; + } + this.__hazzerBits = hazzerBits; + check((initializeMessage != null) ^ header.hasClientToken(), + "There should either be a client token or an initialization request"); + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientHeader getHeader() { return header; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage getNullableInitializeMessage() { return initializeMessage; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationMessage getNullableRegistrationMessage() { return registrationMessage; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncMessage getNullableRegistrationSyncMessage() { return registrationSyncMessage; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage getNullableInvalidationAckMessage() { return invalidationAckMessage; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage getInfoMessage() { return infoMessage; } + public boolean hasInfoMessage() { return (0x1 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ClientToServerMessage)) { return false; } + ClientToServerMessage other = (ClientToServerMessage) obj; + return __hazzerBits == other.__hazzerBits + && equals(header, other.header) + && equals(initializeMessage, other.initializeMessage) + && equals(registrationMessage, other.registrationMessage) + && equals(registrationSyncMessage, other.registrationSyncMessage) + && equals(invalidationAckMessage, other.invalidationAckMessage) + && (!hasInfoMessage() || equals(infoMessage, other.infoMessage)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + header.hashCode(); + if (initializeMessage != null) { + result = result * 31 + initializeMessage.hashCode(); + } + if (registrationMessage != null) { + result = result * 31 + registrationMessage.hashCode(); + } + if (registrationSyncMessage != null) { + result = result * 31 + registrationSyncMessage.hashCode(); + } + if (invalidationAckMessage != null) { + result = result * 31 + invalidationAckMessage.hashCode(); + } + if (hasInfoMessage()) { + result = result * 31 + infoMessage.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ClientToServerMessage:"); + builder.append(" header=").append(header); + if (initializeMessage != null) { + builder.append(" initialize_message=").append(initializeMessage); + } + if (registrationMessage != null) { + builder.append(" registration_message=").append(registrationMessage); + } + if (registrationSyncMessage != null) { + builder.append(" registration_sync_message=").append(registrationSyncMessage); + } + if (invalidationAckMessage != null) { + builder.append(" invalidation_ack_message=").append(invalidationAckMessage); + } + if (hasInfoMessage()) { + builder.append(" info_message=").append(infoMessage); + } + builder.append('>'); + } + + public static ClientToServerMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ClientToServerMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ClientToServerMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ClientToServerMessage message) { + if (message == null) { return null; } + return new ClientToServerMessage(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientHeader.fromMessageNano(message.header), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage.fromMessageNano(message.initializeMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationMessage.fromMessageNano(message.registrationMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncMessage.fromMessageNano(message.registrationSyncMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage.fromMessageNano(message.invalidationAckMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage.fromMessageNano(message.infoMessage)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ClientToServerMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ClientToServerMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ClientToServerMessage(); + msg.header = header.toMessageNano(); + msg.initializeMessage = this.initializeMessage != null ? initializeMessage.toMessageNano() : null; + msg.registrationMessage = this.registrationMessage != null ? registrationMessage.toMessageNano() : null; + msg.registrationSyncMessage = this.registrationSyncMessage != null ? registrationSyncMessage.toMessageNano() : null; + msg.invalidationAckMessage = this.invalidationAckMessage != null ? invalidationAckMessage.toMessageNano() : null; + msg.infoMessage = hasInfoMessage() ? infoMessage.toMessageNano() : null; + return msg; + } + } + + public static final class InitializeMessage extends ProtoWrapper { + public interface DigestSerializationType { + public static final int BYTE_BASED = 1; + public static final int NUMBER_BASED = 2; + } + + public static InitializeMessage create(int clientType, + Bytes nonce, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP applicationClientId, + int digestSerializationType) { + return new InitializeMessage(clientType, nonce, applicationClientId, digestSerializationType); + } + + private final int clientType; + private final Bytes nonce; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP applicationClientId; + private final int digestSerializationType; + + private InitializeMessage(Integer clientType, + Bytes nonce, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP applicationClientId, + Integer digestSerializationType) throws ValidationArgumentException { + required("client_type", clientType); + nonNegative("client_type", clientType); + this.clientType = clientType; + required("nonce", nonce); + this.nonce = nonce; + required("application_client_id", applicationClientId); + this.applicationClientId = applicationClientId; + required("digest_serialization_type", digestSerializationType); + this.digestSerializationType = digestSerializationType; + } + + public int getClientType() { return clientType; } + + public Bytes getNonce() { return nonce; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP getApplicationClientId() { return applicationClientId; } + + public int getDigestSerializationType() { return digestSerializationType; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof InitializeMessage)) { return false; } + InitializeMessage other = (InitializeMessage) obj; + return clientType == other.clientType + && equals(nonce, other.nonce) + && equals(applicationClientId, other.applicationClientId) + && digestSerializationType == other.digestSerializationType; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(clientType); + result = result * 31 + nonce.hashCode(); + result = result * 31 + applicationClientId.hashCode(); + result = result * 31 + hash(digestSerializationType); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<InitializeMessage:"); + builder.append(" client_type=").append(clientType); + builder.append(" nonce=").append(nonce); + builder.append(" application_client_id=").append(applicationClientId); + builder.append(" digest_serialization_type=").append(digestSerializationType); + builder.append('>'); + } + + public static InitializeMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.InitializeMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static InitializeMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.InitializeMessage message) { + if (message == null) { return null; } + return new InitializeMessage(message.clientType, + Bytes.fromByteArray(message.nonce), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ApplicationClientIdP.fromMessageNano(message.applicationClientId), + message.digestSerializationType); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.InitializeMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.InitializeMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.InitializeMessage(); + msg.clientType = clientType; + msg.nonce = nonce.getByteArray(); + msg.applicationClientId = applicationClientId.toMessageNano(); + msg.digestSerializationType = digestSerializationType; + return msg; + } + } + + public static final class RegistrationMessage extends ProtoWrapper { + public static RegistrationMessage create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> registration) { + return new RegistrationMessage(registration); + } + + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> registration; + + private RegistrationMessage(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> registration) throws ValidationArgumentException { + this.registration = required("registration", registration); + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> getRegistration() { return registration; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationMessage)) { return false; } + RegistrationMessage other = (RegistrationMessage) obj; + return equals(registration, other.registration); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + registration.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationMessage:"); + builder.append(" registration=[").append(registration).append(']'); + builder.append('>'); + } + + public static RegistrationMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationMessage message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> registration = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP>(message.registration.length); + for (int i = 0; i < message.registration.length; i++) { + registration.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP.fromMessageNano(message.registration[i])); + } + return new RegistrationMessage(registration); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationMessage(); + msg.registration = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationP[registration.size()]; + for (int i = 0; i < msg.registration.length; i++) { + msg.registration[i] = registration.get(i).toMessageNano(); + } + return msg; + } + } + + public static final class RegistrationSyncMessage extends ProtoWrapper { + public static RegistrationSyncMessage create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> subtree) { + return new RegistrationSyncMessage(subtree); + } + + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> subtree; + + private RegistrationSyncMessage(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> subtree) throws ValidationArgumentException { + this.subtree = required("subtree", subtree); + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> getSubtree() { return subtree; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationSyncMessage)) { return false; } + RegistrationSyncMessage other = (RegistrationSyncMessage) obj; + return equals(subtree, other.subtree); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + subtree.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationSyncMessage:"); + builder.append(" subtree=[").append(subtree).append(']'); + builder.append('>'); + } + + public static RegistrationSyncMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationSyncMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncMessage message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> subtree = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree>(message.subtree.length); + for (int i = 0; i < message.subtree.length; i++) { + subtree.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree.fromMessageNano(message.subtree[i])); + } + return new RegistrationSyncMessage(subtree); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncMessage(); + msg.subtree = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSubtree[subtree.size()]; + for (int i = 0; i < msg.subtree.length; i++) { + msg.subtree[i] = subtree.get(i).toMessageNano(); + } + return msg; + } + } + + public static final class RegistrationSubtree extends ProtoWrapper { + public static RegistrationSubtree create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registeredObject) { + return new RegistrationSubtree(registeredObject); + } + + public static final RegistrationSubtree DEFAULT_INSTANCE = new RegistrationSubtree(null); + + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registeredObject; + + private RegistrationSubtree(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registeredObject) { + this.registeredObject = optional("registered_object", registeredObject); + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> getRegisteredObject() { return registeredObject; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationSubtree)) { return false; } + RegistrationSubtree other = (RegistrationSubtree) obj; + return equals(registeredObject, other.registeredObject); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + registeredObject.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationSubtree:"); + builder.append(" registered_object=[").append(registeredObject).append(']'); + builder.append('>'); + } + + public static RegistrationSubtree parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSubtree(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationSubtree fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSubtree message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registeredObject = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP>(message.registeredObject.length); + for (int i = 0; i < message.registeredObject.length; i++) { + registeredObject.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.registeredObject[i])); + } + return new RegistrationSubtree(registeredObject); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSubtree toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSubtree msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSubtree(); + msg.registeredObject = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP[registeredObject.size()]; + for (int i = 0; i < msg.registeredObject.length; i++) { + msg.registeredObject[i] = registeredObject.get(i).toMessageNano(); + } + return msg; + } + } + + public static final class InfoMessage extends ProtoWrapper { + public static InfoMessage create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientVersion clientVersion, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> configParameter, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> performanceCounter, + Boolean serverRegistrationSummaryRequested, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig) { + return new InfoMessage(clientVersion, configParameter, performanceCounter, serverRegistrationSummaryRequested, clientConfig); + } + + public static final InfoMessage DEFAULT_INSTANCE = new InfoMessage(null, null, null, null, null); + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientVersion clientVersion; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> configParameter; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> performanceCounter; + private final boolean serverRegistrationSummaryRequested; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig; + + private InfoMessage(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientVersion clientVersion, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> configParameter, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> performanceCounter, + Boolean serverRegistrationSummaryRequested, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP clientConfig) { + int hazzerBits = 0; + this.clientVersion = clientVersion; + this.configParameter = optional("config_parameter", configParameter); + this.performanceCounter = optional("performance_counter", performanceCounter); + if (serverRegistrationSummaryRequested != null) { + hazzerBits |= 0x1; + this.serverRegistrationSummaryRequested = serverRegistrationSummaryRequested; + } else { + this.serverRegistrationSummaryRequested = false; + } + this.clientConfig = clientConfig; + this.__hazzerBits = hazzerBits; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientVersion getNullableClientVersion() { return clientVersion; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> getConfigParameter() { return configParameter; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> getPerformanceCounter() { return performanceCounter; } + + public boolean getServerRegistrationSummaryRequested() { return serverRegistrationSummaryRequested; } + public boolean hasServerRegistrationSummaryRequested() { return (0x1 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP getNullableClientConfig() { return clientConfig; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof InfoMessage)) { return false; } + InfoMessage other = (InfoMessage) obj; + return __hazzerBits == other.__hazzerBits + && equals(clientVersion, other.clientVersion) + && equals(configParameter, other.configParameter) + && equals(performanceCounter, other.performanceCounter) + && (!hasServerRegistrationSummaryRequested() || serverRegistrationSummaryRequested == other.serverRegistrationSummaryRequested) + && equals(clientConfig, other.clientConfig); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (clientVersion != null) { + result = result * 31 + clientVersion.hashCode(); + } + result = result * 31 + configParameter.hashCode(); + result = result * 31 + performanceCounter.hashCode(); + if (hasServerRegistrationSummaryRequested()) { + result = result * 31 + hash(serverRegistrationSummaryRequested); + } + if (clientConfig != null) { + result = result * 31 + clientConfig.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<InfoMessage:"); + if (clientVersion != null) { + builder.append(" client_version=").append(clientVersion); + } + builder.append(" config_parameter=[").append(configParameter).append(']'); + builder.append(" performance_counter=[").append(performanceCounter).append(']'); + if (hasServerRegistrationSummaryRequested()) { + builder.append(" server_registration_summary_requested=").append(serverRegistrationSummaryRequested); + } + if (clientConfig != null) { + builder.append(" client_config=").append(clientConfig); + } + builder.append('>'); + } + + public static InfoMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.InfoMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static InfoMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.InfoMessage message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> configParameter = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord>(message.configParameter.length); + for (int i = 0; i < message.configParameter.length; i++) { + configParameter.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord.fromMessageNano(message.configParameter[i])); + } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> performanceCounter = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord>(message.performanceCounter.length); + for (int i = 0; i < message.performanceCounter.length; i++) { + performanceCounter.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord.fromMessageNano(message.performanceCounter[i])); + } + return new InfoMessage(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientVersion.fromMessageNano(message.clientVersion), + configParameter, + performanceCounter, + message.serverRegistrationSummaryRequested, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientConfigP.fromMessageNano(message.clientConfig)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.InfoMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.InfoMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.InfoMessage(); + msg.clientVersion = this.clientVersion != null ? clientVersion.toMessageNano() : null; + msg.configParameter = new com.google.protos.ipc.invalidation.NanoClientProtocol.PropertyRecord[configParameter.size()]; + for (int i = 0; i < msg.configParameter.length; i++) { + msg.configParameter[i] = configParameter.get(i).toMessageNano(); + } + msg.performanceCounter = new com.google.protos.ipc.invalidation.NanoClientProtocol.PropertyRecord[performanceCounter.size()]; + for (int i = 0; i < msg.performanceCounter.length; i++) { + msg.performanceCounter[i] = performanceCounter.get(i).toMessageNano(); + } + msg.serverRegistrationSummaryRequested = hasServerRegistrationSummaryRequested() ? serverRegistrationSummaryRequested : null; + msg.clientConfig = this.clientConfig != null ? clientConfig.toMessageNano() : null; + return msg; + } + } + + public static final class PropertyRecord extends ProtoWrapper { + public static PropertyRecord create(String name, + Integer value) { + return new PropertyRecord(name, value); + } + + public static final PropertyRecord DEFAULT_INSTANCE = new PropertyRecord(null, null); + + private final long __hazzerBits; + private final String name; + private final int value; + + private PropertyRecord(String name, + Integer value) { + int hazzerBits = 0; + if (name != null) { + hazzerBits |= 0x1; + this.name = name; + } else { + this.name = ""; + } + if (value != null) { + hazzerBits |= 0x2; + this.value = value; + } else { + this.value = 0; + } + this.__hazzerBits = hazzerBits; + } + + public String getName() { return name; } + public boolean hasName() { return (0x1 & __hazzerBits) != 0; } + + public int getValue() { return value; } + public boolean hasValue() { return (0x2 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof PropertyRecord)) { return false; } + PropertyRecord other = (PropertyRecord) obj; + return __hazzerBits == other.__hazzerBits + && (!hasName() || equals(name, other.name)) + && (!hasValue() || value == other.value); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasName()) { + result = result * 31 + name.hashCode(); + } + if (hasValue()) { + result = result * 31 + hash(value); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<PropertyRecord:"); + if (hasName()) { + builder.append(" name=").append(name); + } + if (hasValue()) { + builder.append(" value=").append(value); + } + builder.append('>'); + } + + public static PropertyRecord parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.PropertyRecord(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static PropertyRecord fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.PropertyRecord message) { + if (message == null) { return null; } + return new PropertyRecord(message.name, + message.value); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.PropertyRecord toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.PropertyRecord msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.PropertyRecord(); + msg.name = hasName() ? name : null; + msg.value = hasValue() ? value : null; + return msg; + } + } + + public static final class ServerHeader extends ProtoWrapper { + public static final class Builder { + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion protocolVersion; + public Bytes clientToken; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary registrationSummary; + public long serverTimeMs; + public String messageId; + public Builder(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion protocolVersion, + Bytes clientToken, + long serverTimeMs) { + this.protocolVersion = protocolVersion;this.clientToken = clientToken;this.serverTimeMs = serverTimeMs;} + + public ServerHeader build() { + return new ServerHeader(protocolVersion, clientToken, registrationSummary, serverTimeMs, messageId); + } + } + + public static ServerHeader create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion protocolVersion, + Bytes clientToken, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary registrationSummary, + long serverTimeMs, + String messageId) { + return new ServerHeader(protocolVersion, clientToken, registrationSummary, serverTimeMs, messageId); + } + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion protocolVersion; + private final Bytes clientToken; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary registrationSummary; + private final long serverTimeMs; + private final String messageId; + + private ServerHeader(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion protocolVersion, + Bytes clientToken, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary registrationSummary, + Long serverTimeMs, + String messageId) throws ValidationArgumentException { + int hazzerBits = 0; + required("protocol_version", protocolVersion); + this.protocolVersion = protocolVersion; + required("client_token", clientToken); + nonEmpty("client_token", clientToken); + this.clientToken = clientToken; + this.registrationSummary = registrationSummary; + required("server_time_ms", serverTimeMs); + nonNegative("server_time_ms", serverTimeMs); + this.serverTimeMs = serverTimeMs; + if (messageId != null) { + hazzerBits |= 0x1; + nonEmpty("message_id", messageId); + this.messageId = messageId; + } else { + this.messageId = ""; + } + this.__hazzerBits = hazzerBits; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion getProtocolVersion() { return protocolVersion; } + + public Bytes getClientToken() { return clientToken; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary getNullableRegistrationSummary() { return registrationSummary; } + + public long getServerTimeMs() { return serverTimeMs; } + + public String getMessageId() { return messageId; } + public boolean hasMessageId() { return (0x1 & __hazzerBits) != 0; } + + public Builder toBuilder() { + Builder builder = new Builder(protocolVersion, clientToken, serverTimeMs); + if (this.registrationSummary != null) { + builder.registrationSummary = registrationSummary; + } + if (hasMessageId()) { + builder.messageId = messageId; + } + return builder; + } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ServerHeader)) { return false; } + ServerHeader other = (ServerHeader) obj; + return __hazzerBits == other.__hazzerBits + && equals(protocolVersion, other.protocolVersion) + && equals(clientToken, other.clientToken) + && equals(registrationSummary, other.registrationSummary) + && serverTimeMs == other.serverTimeMs + && (!hasMessageId() || equals(messageId, other.messageId)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + protocolVersion.hashCode(); + result = result * 31 + clientToken.hashCode(); + if (registrationSummary != null) { + result = result * 31 + registrationSummary.hashCode(); + } + result = result * 31 + hash(serverTimeMs); + if (hasMessageId()) { + result = result * 31 + messageId.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ServerHeader:"); + builder.append(" protocol_version=").append(protocolVersion); + builder.append(" client_token=").append(clientToken); + if (registrationSummary != null) { + builder.append(" registration_summary=").append(registrationSummary); + } + builder.append(" server_time_ms=").append(serverTimeMs); + if (hasMessageId()) { + builder.append(" message_id=").append(messageId); + } + builder.append('>'); + } + + public static ServerHeader parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ServerHeader(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ServerHeader fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ServerHeader message) { + if (message == null) { return null; } + return new ServerHeader(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolVersion.fromMessageNano(message.protocolVersion), + Bytes.fromByteArray(message.clientToken), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary.fromMessageNano(message.registrationSummary), + message.serverTimeMs, + message.messageId); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ServerHeader toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ServerHeader msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ServerHeader(); + msg.protocolVersion = protocolVersion.toMessageNano(); + msg.clientToken = clientToken.getByteArray(); + msg.registrationSummary = this.registrationSummary != null ? registrationSummary.toMessageNano() : null; + msg.serverTimeMs = serverTimeMs; + msg.messageId = hasMessageId() ? messageId : null; + return msg; + } + } + + public static final class ServerToClientMessage extends ProtoWrapper { + public static final class Builder { + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader header; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.TokenControlMessage tokenControlMessage; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage invalidationMessage; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatusMessage registrationStatusMessage; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncRequestMessage registrationSyncRequestMessage; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ConfigChangeMessage configChangeMessage; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoRequestMessage infoRequestMessage; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ErrorMessage errorMessage; + public Builder(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader header) { + this.header = header;} + + public ServerToClientMessage build() { + return new ServerToClientMessage(header, tokenControlMessage, invalidationMessage, registrationStatusMessage, registrationSyncRequestMessage, configChangeMessage, infoRequestMessage, errorMessage); + } + } + + public static ServerToClientMessage create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader header, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.TokenControlMessage tokenControlMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage invalidationMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatusMessage registrationStatusMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncRequestMessage registrationSyncRequestMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ConfigChangeMessage configChangeMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoRequestMessage infoRequestMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ErrorMessage errorMessage) { + return new ServerToClientMessage(header, tokenControlMessage, invalidationMessage, registrationStatusMessage, registrationSyncRequestMessage, configChangeMessage, infoRequestMessage, errorMessage); + } + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader header; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.TokenControlMessage tokenControlMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage invalidationMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatusMessage registrationStatusMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncRequestMessage registrationSyncRequestMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ConfigChangeMessage configChangeMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoRequestMessage infoRequestMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ErrorMessage errorMessage; + + private ServerToClientMessage(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader header, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.TokenControlMessage tokenControlMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage invalidationMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatusMessage registrationStatusMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncRequestMessage registrationSyncRequestMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ConfigChangeMessage configChangeMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoRequestMessage infoRequestMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ErrorMessage errorMessage) throws ValidationArgumentException { + int hazzerBits = 0; + required("header", header); + this.header = header; + if (tokenControlMessage != null) { + hazzerBits |= 0x1; + this.tokenControlMessage = tokenControlMessage; + } else { + this.tokenControlMessage = com.google.ipc.invalidation.ticl.proto.ClientProtocol.TokenControlMessage.DEFAULT_INSTANCE; + } + this.invalidationMessage = invalidationMessage; + this.registrationStatusMessage = registrationStatusMessage; + if (registrationSyncRequestMessage != null) { + hazzerBits |= 0x2; + this.registrationSyncRequestMessage = registrationSyncRequestMessage; + } else { + this.registrationSyncRequestMessage = com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncRequestMessage.DEFAULT_INSTANCE; + } + if (configChangeMessage != null) { + hazzerBits |= 0x4; + this.configChangeMessage = configChangeMessage; + } else { + this.configChangeMessage = com.google.ipc.invalidation.ticl.proto.ClientProtocol.ConfigChangeMessage.DEFAULT_INSTANCE; + } + this.infoRequestMessage = infoRequestMessage; + this.errorMessage = errorMessage; + this.__hazzerBits = hazzerBits; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader getHeader() { return header; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.TokenControlMessage getTokenControlMessage() { return tokenControlMessage; } + public boolean hasTokenControlMessage() { return (0x1 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage getNullableInvalidationMessage() { return invalidationMessage; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatusMessage getNullableRegistrationStatusMessage() { return registrationStatusMessage; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncRequestMessage getRegistrationSyncRequestMessage() { return registrationSyncRequestMessage; } + public boolean hasRegistrationSyncRequestMessage() { return (0x2 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ConfigChangeMessage getConfigChangeMessage() { return configChangeMessage; } + public boolean hasConfigChangeMessage() { return (0x4 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoRequestMessage getNullableInfoRequestMessage() { return infoRequestMessage; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ErrorMessage getNullableErrorMessage() { return errorMessage; } + + public Builder toBuilder() { + Builder builder = new Builder(header); + if (hasTokenControlMessage()) { + builder.tokenControlMessage = tokenControlMessage; + } + if (this.invalidationMessage != null) { + builder.invalidationMessage = invalidationMessage; + } + if (this.registrationStatusMessage != null) { + builder.registrationStatusMessage = registrationStatusMessage; + } + if (hasRegistrationSyncRequestMessage()) { + builder.registrationSyncRequestMessage = registrationSyncRequestMessage; + } + if (hasConfigChangeMessage()) { + builder.configChangeMessage = configChangeMessage; + } + if (this.infoRequestMessage != null) { + builder.infoRequestMessage = infoRequestMessage; + } + if (this.errorMessage != null) { + builder.errorMessage = errorMessage; + } + return builder; + } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ServerToClientMessage)) { return false; } + ServerToClientMessage other = (ServerToClientMessage) obj; + return __hazzerBits == other.__hazzerBits + && equals(header, other.header) + && (!hasTokenControlMessage() || equals(tokenControlMessage, other.tokenControlMessage)) + && equals(invalidationMessage, other.invalidationMessage) + && equals(registrationStatusMessage, other.registrationStatusMessage) + && (!hasRegistrationSyncRequestMessage() || equals(registrationSyncRequestMessage, other.registrationSyncRequestMessage)) + && (!hasConfigChangeMessage() || equals(configChangeMessage, other.configChangeMessage)) + && equals(infoRequestMessage, other.infoRequestMessage) + && equals(errorMessage, other.errorMessage); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + header.hashCode(); + if (hasTokenControlMessage()) { + result = result * 31 + tokenControlMessage.hashCode(); + } + if (invalidationMessage != null) { + result = result * 31 + invalidationMessage.hashCode(); + } + if (registrationStatusMessage != null) { + result = result * 31 + registrationStatusMessage.hashCode(); + } + if (hasRegistrationSyncRequestMessage()) { + result = result * 31 + registrationSyncRequestMessage.hashCode(); + } + if (hasConfigChangeMessage()) { + result = result * 31 + configChangeMessage.hashCode(); + } + if (infoRequestMessage != null) { + result = result * 31 + infoRequestMessage.hashCode(); + } + if (errorMessage != null) { + result = result * 31 + errorMessage.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ServerToClientMessage:"); + builder.append(" header=").append(header); + if (hasTokenControlMessage()) { + builder.append(" token_control_message=").append(tokenControlMessage); + } + if (invalidationMessage != null) { + builder.append(" invalidation_message=").append(invalidationMessage); + } + if (registrationStatusMessage != null) { + builder.append(" registration_status_message=").append(registrationStatusMessage); + } + if (hasRegistrationSyncRequestMessage()) { + builder.append(" registration_sync_request_message=").append(registrationSyncRequestMessage); + } + if (hasConfigChangeMessage()) { + builder.append(" config_change_message=").append(configChangeMessage); + } + if (infoRequestMessage != null) { + builder.append(" info_request_message=").append(infoRequestMessage); + } + if (errorMessage != null) { + builder.append(" error_message=").append(errorMessage); + } + builder.append('>'); + } + + public static ServerToClientMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ServerToClientMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ServerToClientMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ServerToClientMessage message) { + if (message == null) { return null; } + return new ServerToClientMessage(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader.fromMessageNano(message.header), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.TokenControlMessage.fromMessageNano(message.tokenControlMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationMessage.fromMessageNano(message.invalidationMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatusMessage.fromMessageNano(message.registrationStatusMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSyncRequestMessage.fromMessageNano(message.registrationSyncRequestMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ConfigChangeMessage.fromMessageNano(message.configChangeMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoRequestMessage.fromMessageNano(message.infoRequestMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ErrorMessage.fromMessageNano(message.errorMessage)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ServerToClientMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ServerToClientMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ServerToClientMessage(); + msg.header = header.toMessageNano(); + msg.tokenControlMessage = hasTokenControlMessage() ? tokenControlMessage.toMessageNano() : null; + msg.invalidationMessage = this.invalidationMessage != null ? invalidationMessage.toMessageNano() : null; + msg.registrationStatusMessage = this.registrationStatusMessage != null ? registrationStatusMessage.toMessageNano() : null; + msg.registrationSyncRequestMessage = hasRegistrationSyncRequestMessage() ? registrationSyncRequestMessage.toMessageNano() : null; + msg.configChangeMessage = hasConfigChangeMessage() ? configChangeMessage.toMessageNano() : null; + msg.infoRequestMessage = this.infoRequestMessage != null ? infoRequestMessage.toMessageNano() : null; + msg.errorMessage = this.errorMessage != null ? errorMessage.toMessageNano() : null; + return msg; + } + } + + public static final class TokenControlMessage extends ProtoWrapper { + public static TokenControlMessage create(Bytes newToken) { + return new TokenControlMessage(newToken); + } + + public static final TokenControlMessage DEFAULT_INSTANCE = new TokenControlMessage(null); + + private final long __hazzerBits; + private final Bytes newToken; + + private TokenControlMessage(Bytes newToken) { + int hazzerBits = 0; + if (newToken != null) { + hazzerBits |= 0x1; + this.newToken = newToken; + } else { + this.newToken = Bytes.EMPTY_BYTES; + } + this.__hazzerBits = hazzerBits; + } + + public Bytes getNewToken() { return newToken; } + public boolean hasNewToken() { return (0x1 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof TokenControlMessage)) { return false; } + TokenControlMessage other = (TokenControlMessage) obj; + return __hazzerBits == other.__hazzerBits + && (!hasNewToken() || equals(newToken, other.newToken)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasNewToken()) { + result = result * 31 + newToken.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<TokenControlMessage:"); + if (hasNewToken()) { + builder.append(" new_token=").append(newToken); + } + builder.append('>'); + } + + public static TokenControlMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.TokenControlMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static TokenControlMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.TokenControlMessage message) { + if (message == null) { return null; } + return new TokenControlMessage(Bytes.fromByteArray(message.newToken)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.TokenControlMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.TokenControlMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.TokenControlMessage(); + msg.newToken = hasNewToken() ? newToken.getByteArray() : null; + return msg; + } + } + + public static final class RegistrationStatus extends ProtoWrapper { + public static RegistrationStatus create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP registration, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.StatusP status) { + return new RegistrationStatus(registration, status); + } + + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP registration; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.StatusP status; + + private RegistrationStatus(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP registration, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.StatusP status) throws ValidationArgumentException { + required("registration", registration); + this.registration = registration; + required("status", status); + this.status = status; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP getRegistration() { return registration; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.StatusP getStatus() { return status; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationStatus)) { return false; } + RegistrationStatus other = (RegistrationStatus) obj; + return equals(registration, other.registration) + && equals(status, other.status); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + registration.hashCode(); + result = result * 31 + status.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationStatus:"); + builder.append(" registration=").append(registration); + builder.append(" status=").append(status); + builder.append('>'); + } + + public static RegistrationStatus parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatus(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationStatus fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatus message) { + if (message == null) { return null; } + return new RegistrationStatus(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP.fromMessageNano(message.registration), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.StatusP.fromMessageNano(message.status)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatus toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatus msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatus(); + msg.registration = registration.toMessageNano(); + msg.status = status.toMessageNano(); + return msg; + } + } + + public static final class RegistrationStatusMessage extends ProtoWrapper { + public static RegistrationStatusMessage create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus> registrationStatus) { + return new RegistrationStatusMessage(registrationStatus); + } + + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus> registrationStatus; + + private RegistrationStatusMessage(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus> registrationStatus) throws ValidationArgumentException { + this.registrationStatus = required("registration_status", registrationStatus); + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus> getRegistrationStatus() { return registrationStatus; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationStatusMessage)) { return false; } + RegistrationStatusMessage other = (RegistrationStatusMessage) obj; + return equals(registrationStatus, other.registrationStatus); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + registrationStatus.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationStatusMessage:"); + builder.append(" registration_status=[").append(registrationStatus).append(']'); + builder.append('>'); + } + + public static RegistrationStatusMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatusMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationStatusMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatusMessage message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus> registrationStatus = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus>(message.registrationStatus.length); + for (int i = 0; i < message.registrationStatus.length; i++) { + registrationStatus.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus.fromMessageNano(message.registrationStatus[i])); + } + return new RegistrationStatusMessage(registrationStatus); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatusMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatusMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatusMessage(); + msg.registrationStatus = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationStatus[registrationStatus.size()]; + for (int i = 0; i < msg.registrationStatus.length; i++) { + msg.registrationStatus[i] = registrationStatus.get(i).toMessageNano(); + } + return msg; + } + } + + public static final class RegistrationSyncRequestMessage extends ProtoWrapper { + public static RegistrationSyncRequestMessage create() { + return new RegistrationSyncRequestMessage(); + } + + public static final RegistrationSyncRequestMessage DEFAULT_INSTANCE = new RegistrationSyncRequestMessage(); + + + private RegistrationSyncRequestMessage() { + } + + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationSyncRequestMessage)) { return false; } + RegistrationSyncRequestMessage other = (RegistrationSyncRequestMessage) obj; + return true; + } + + @Override protected int computeHashCode() { + int result = 1; + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationSyncRequestMessage:"); + builder.append('>'); + } + + public static RegistrationSyncRequestMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncRequestMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationSyncRequestMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncRequestMessage message) { + if (message == null) { return null; } + return new RegistrationSyncRequestMessage(); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncRequestMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncRequestMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSyncRequestMessage(); + return msg; + } + } + + public static final class InvalidationMessage extends ProtoWrapper { + public static InvalidationMessage create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> invalidation) { + return new InvalidationMessage(invalidation); + } + + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> invalidation; + + private InvalidationMessage(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> invalidation) throws ValidationArgumentException { + this.invalidation = required("invalidation", invalidation); + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> getInvalidation() { return invalidation; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof InvalidationMessage)) { return false; } + InvalidationMessage other = (InvalidationMessage) obj; + return equals(invalidation, other.invalidation); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + invalidation.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<InvalidationMessage:"); + builder.append(" invalidation=[").append(invalidation).append(']'); + builder.append('>'); + } + + public static InvalidationMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static InvalidationMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationMessage message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> invalidation = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP>(message.invalidation.length); + for (int i = 0; i < message.invalidation.length; i++) { + invalidation.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP.fromMessageNano(message.invalidation[i])); + } + return new InvalidationMessage(invalidation); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationMessage(); + msg.invalidation = new com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationP[invalidation.size()]; + for (int i = 0; i < msg.invalidation.length; i++) { + msg.invalidation[i] = invalidation.get(i).toMessageNano(); + } + return msg; + } + } + + public static final class InfoRequestMessage extends ProtoWrapper { + public interface InfoType { + public static final int GET_PERFORMANCE_COUNTERS = 1; + } + + public static InfoRequestMessage create(Collection<Integer> infoType) { + return new InfoRequestMessage(infoType); + } + + private final List<Integer> infoType; + + private InfoRequestMessage(Collection<Integer> infoType) throws ValidationArgumentException { + this.infoType = required("info_type", infoType); + } + + public List<Integer> getInfoType() { return infoType; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof InfoRequestMessage)) { return false; } + InfoRequestMessage other = (InfoRequestMessage) obj; + return equals(infoType, other.infoType); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + infoType.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<InfoRequestMessage:"); + builder.append(" info_type=[").append(infoType).append(']'); + builder.append('>'); + } + + public static InfoRequestMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.InfoRequestMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static InfoRequestMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.InfoRequestMessage message) { + if (message == null) { return null; } + List<Integer> infoType = new ArrayList<Integer>(message.infoType.length); + for (int i = 0; i < message.infoType.length; i++) { + infoType.add(message.infoType[i]); + } + return new InfoRequestMessage(infoType); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.InfoRequestMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.InfoRequestMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.InfoRequestMessage(); + msg.infoType = new int[infoType.size()]; + for (int i = 0; i < msg.infoType.length; i++) { + msg.infoType[i] = infoType.get(i); + } + return msg; + } + } + + public static final class RateLimitP extends ProtoWrapper { + public static RateLimitP create(int windowMs, + int count) { + return new RateLimitP(windowMs, count); + } + + private final int windowMs; + private final int count; + + private RateLimitP(Integer windowMs, + Integer count) throws ValidationArgumentException { + required("window_ms", windowMs); + this.windowMs = windowMs; + required("count", count); + this.count = count; + check(windowMs >= 1000 && windowMs > count, "Invalid window_ms and count"); + } + + public int getWindowMs() { return windowMs; } + + public int getCount() { return count; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RateLimitP)) { return false; } + RateLimitP other = (RateLimitP) obj; + return windowMs == other.windowMs + && count == other.count; + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(windowMs); + result = result * 31 + hash(count); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RateLimitP:"); + builder.append(" window_ms=").append(windowMs); + builder.append(" count=").append(count); + builder.append('>'); + } + + public static RateLimitP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.RateLimitP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RateLimitP fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.RateLimitP message) { + if (message == null) { return null; } + return new RateLimitP(message.windowMs, + message.count); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.RateLimitP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.RateLimitP msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.RateLimitP(); + msg.windowMs = windowMs; + msg.count = count; + return msg; + } + } + + public static final class ProtocolHandlerConfigP extends ProtoWrapper { + public static final class Builder { + public Integer batchingDelayMs; + public Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP> rateLimit; + public Builder() { + } + + public ProtocolHandlerConfigP build() { + return new ProtocolHandlerConfigP(batchingDelayMs, rateLimit); + } + } + + public static ProtocolHandlerConfigP create(Integer batchingDelayMs, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP> rateLimit) { + return new ProtocolHandlerConfigP(batchingDelayMs, rateLimit); + } + + public static final ProtocolHandlerConfigP DEFAULT_INSTANCE = new ProtocolHandlerConfigP(null, null); + + private final long __hazzerBits; + private final int batchingDelayMs; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP> rateLimit; + + private ProtocolHandlerConfigP(Integer batchingDelayMs, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP> rateLimit) { + int hazzerBits = 0; + if (batchingDelayMs != null) { + hazzerBits |= 0x1; + this.batchingDelayMs = batchingDelayMs; + } else { + this.batchingDelayMs = 500; + } + this.rateLimit = optional("rate_limit", rateLimit); + this.__hazzerBits = hazzerBits; + } + + public int getBatchingDelayMs() { return batchingDelayMs; } + public boolean hasBatchingDelayMs() { return (0x1 & __hazzerBits) != 0; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP> getRateLimit() { return rateLimit; } + + public Builder toBuilder() { + Builder builder = new Builder(); + if (hasBatchingDelayMs()) { + builder.batchingDelayMs = batchingDelayMs; + } + if (!this.rateLimit.isEmpty()) { + builder.rateLimit = rateLimit; + } + return builder; + } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ProtocolHandlerConfigP)) { return false; } + ProtocolHandlerConfigP other = (ProtocolHandlerConfigP) obj; + return __hazzerBits == other.__hazzerBits + && (!hasBatchingDelayMs() || batchingDelayMs == other.batchingDelayMs) + && equals(rateLimit, other.rateLimit); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasBatchingDelayMs()) { + result = result * 31 + hash(batchingDelayMs); + } + result = result * 31 + rateLimit.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ProtocolHandlerConfigP:"); + if (hasBatchingDelayMs()) { + builder.append(" batching_delay_ms=").append(batchingDelayMs); + } + builder.append(" rate_limit=[").append(rateLimit).append(']'); + builder.append('>'); + } + + public static ProtocolHandlerConfigP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolHandlerConfigP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ProtocolHandlerConfigP fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolHandlerConfigP message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP> rateLimit = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP>(message.rateLimit.length); + for (int i = 0; i < message.rateLimit.length; i++) { + rateLimit.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RateLimitP.fromMessageNano(message.rateLimit[i])); + } + return new ProtocolHandlerConfigP(message.batchingDelayMs, + rateLimit); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolHandlerConfigP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolHandlerConfigP msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ProtocolHandlerConfigP(); + msg.batchingDelayMs = hasBatchingDelayMs() ? batchingDelayMs : null; + msg.rateLimit = new com.google.protos.ipc.invalidation.NanoClientProtocol.RateLimitP[rateLimit.size()]; + for (int i = 0; i < msg.rateLimit.length; i++) { + msg.rateLimit[i] = rateLimit.get(i).toMessageNano(); + } + return msg; + } + } + + public static final class ClientConfigP extends ProtoWrapper { + public static final class Builder { + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + public Integer networkTimeoutDelayMs; + public Integer writeRetryDelayMs; + public Integer heartbeatIntervalMs; + public Integer perfCounterDelayMs; + public Integer maxExponentialBackoffFactor; + public Integer smearPercent; + public Boolean isTransient; + public Integer initialPersistentHeartbeatDelayMs; + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP protocolHandlerConfig; + public Boolean channelSupportsOfflineDelivery; + public Integer offlineHeartbeatThresholdMs; + public Boolean allowSuppression; + public Builder(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP protocolHandlerConfig) { + this.version = version;this.protocolHandlerConfig = protocolHandlerConfig;} + + public ClientConfigP build() { + return new ClientConfigP(version, networkTimeoutDelayMs, writeRetryDelayMs, heartbeatIntervalMs, perfCounterDelayMs, maxExponentialBackoffFactor, smearPercent, isTransient, initialPersistentHeartbeatDelayMs, protocolHandlerConfig, channelSupportsOfflineDelivery, offlineHeartbeatThresholdMs, allowSuppression); + } + } + + public static ClientConfigP create(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + Integer networkTimeoutDelayMs, + Integer writeRetryDelayMs, + Integer heartbeatIntervalMs, + Integer perfCounterDelayMs, + Integer maxExponentialBackoffFactor, + Integer smearPercent, + Boolean isTransient, + Integer initialPersistentHeartbeatDelayMs, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP protocolHandlerConfig, + Boolean channelSupportsOfflineDelivery, + Integer offlineHeartbeatThresholdMs, + Boolean allowSuppression) { + return new ClientConfigP(version, networkTimeoutDelayMs, writeRetryDelayMs, heartbeatIntervalMs, perfCounterDelayMs, maxExponentialBackoffFactor, smearPercent, isTransient, initialPersistentHeartbeatDelayMs, protocolHandlerConfig, channelSupportsOfflineDelivery, offlineHeartbeatThresholdMs, allowSuppression); + } + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version; + private final int networkTimeoutDelayMs; + private final int writeRetryDelayMs; + private final int heartbeatIntervalMs; + private final int perfCounterDelayMs; + private final int maxExponentialBackoffFactor; + private final int smearPercent; + private final boolean isTransient; + private final int initialPersistentHeartbeatDelayMs; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP protocolHandlerConfig; + private final boolean channelSupportsOfflineDelivery; + private final int offlineHeartbeatThresholdMs; + private final boolean allowSuppression; + + private ClientConfigP(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version version, + Integer networkTimeoutDelayMs, + Integer writeRetryDelayMs, + Integer heartbeatIntervalMs, + Integer perfCounterDelayMs, + Integer maxExponentialBackoffFactor, + Integer smearPercent, + Boolean isTransient, + Integer initialPersistentHeartbeatDelayMs, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP protocolHandlerConfig, + Boolean channelSupportsOfflineDelivery, + Integer offlineHeartbeatThresholdMs, + Boolean allowSuppression) throws ValidationArgumentException { + int hazzerBits = 0; + required("version", version); + this.version = version; + if (networkTimeoutDelayMs != null) { + hazzerBits |= 0x1; + this.networkTimeoutDelayMs = networkTimeoutDelayMs; + } else { + this.networkTimeoutDelayMs = 60000; + } + if (writeRetryDelayMs != null) { + hazzerBits |= 0x2; + this.writeRetryDelayMs = writeRetryDelayMs; + } else { + this.writeRetryDelayMs = 10000; + } + if (heartbeatIntervalMs != null) { + hazzerBits |= 0x4; + this.heartbeatIntervalMs = heartbeatIntervalMs; + } else { + this.heartbeatIntervalMs = 1200000; + } + if (perfCounterDelayMs != null) { + hazzerBits |= 0x8; + this.perfCounterDelayMs = perfCounterDelayMs; + } else { + this.perfCounterDelayMs = 21600000; + } + if (maxExponentialBackoffFactor != null) { + hazzerBits |= 0x10; + this.maxExponentialBackoffFactor = maxExponentialBackoffFactor; + } else { + this.maxExponentialBackoffFactor = 500; + } + if (smearPercent != null) { + hazzerBits |= 0x20; + this.smearPercent = smearPercent; + } else { + this.smearPercent = 20; + } + if (isTransient != null) { + hazzerBits |= 0x40; + this.isTransient = isTransient; + } else { + this.isTransient = false; + } + if (initialPersistentHeartbeatDelayMs != null) { + hazzerBits |= 0x80; + this.initialPersistentHeartbeatDelayMs = initialPersistentHeartbeatDelayMs; + } else { + this.initialPersistentHeartbeatDelayMs = 2000; + } + required("protocol_handler_config", protocolHandlerConfig); + this.protocolHandlerConfig = protocolHandlerConfig; + if (channelSupportsOfflineDelivery != null) { + hazzerBits |= 0x100; + this.channelSupportsOfflineDelivery = channelSupportsOfflineDelivery; + } else { + this.channelSupportsOfflineDelivery = false; + } + if (offlineHeartbeatThresholdMs != null) { + hazzerBits |= 0x200; + this.offlineHeartbeatThresholdMs = offlineHeartbeatThresholdMs; + } else { + this.offlineHeartbeatThresholdMs = 60000; + } + if (allowSuppression != null) { + hazzerBits |= 0x400; + this.allowSuppression = allowSuppression; + } else { + this.allowSuppression = true; + } + this.__hazzerBits = hazzerBits; + } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version getVersion() { return version; } + + public int getNetworkTimeoutDelayMs() { return networkTimeoutDelayMs; } + public boolean hasNetworkTimeoutDelayMs() { return (0x1 & __hazzerBits) != 0; } + + public int getWriteRetryDelayMs() { return writeRetryDelayMs; } + public boolean hasWriteRetryDelayMs() { return (0x2 & __hazzerBits) != 0; } + + public int getHeartbeatIntervalMs() { return heartbeatIntervalMs; } + public boolean hasHeartbeatIntervalMs() { return (0x4 & __hazzerBits) != 0; } + + public int getPerfCounterDelayMs() { return perfCounterDelayMs; } + public boolean hasPerfCounterDelayMs() { return (0x8 & __hazzerBits) != 0; } + + public int getMaxExponentialBackoffFactor() { return maxExponentialBackoffFactor; } + public boolean hasMaxExponentialBackoffFactor() { return (0x10 & __hazzerBits) != 0; } + + public int getSmearPercent() { return smearPercent; } + public boolean hasSmearPercent() { return (0x20 & __hazzerBits) != 0; } + + public boolean getIsTransient() { return isTransient; } + public boolean hasIsTransient() { return (0x40 & __hazzerBits) != 0; } + + public int getInitialPersistentHeartbeatDelayMs() { return initialPersistentHeartbeatDelayMs; } + public boolean hasInitialPersistentHeartbeatDelayMs() { return (0x80 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP getProtocolHandlerConfig() { return protocolHandlerConfig; } + + public boolean getChannelSupportsOfflineDelivery() { return channelSupportsOfflineDelivery; } + public boolean hasChannelSupportsOfflineDelivery() { return (0x100 & __hazzerBits) != 0; } + + public int getOfflineHeartbeatThresholdMs() { return offlineHeartbeatThresholdMs; } + public boolean hasOfflineHeartbeatThresholdMs() { return (0x200 & __hazzerBits) != 0; } + + public boolean getAllowSuppression() { return allowSuppression; } + public boolean hasAllowSuppression() { return (0x400 & __hazzerBits) != 0; } + + public Builder toBuilder() { + Builder builder = new Builder(version, protocolHandlerConfig); + if (hasNetworkTimeoutDelayMs()) { + builder.networkTimeoutDelayMs = networkTimeoutDelayMs; + } + if (hasWriteRetryDelayMs()) { + builder.writeRetryDelayMs = writeRetryDelayMs; + } + if (hasHeartbeatIntervalMs()) { + builder.heartbeatIntervalMs = heartbeatIntervalMs; + } + if (hasPerfCounterDelayMs()) { + builder.perfCounterDelayMs = perfCounterDelayMs; + } + if (hasMaxExponentialBackoffFactor()) { + builder.maxExponentialBackoffFactor = maxExponentialBackoffFactor; + } + if (hasSmearPercent()) { + builder.smearPercent = smearPercent; + } + if (hasIsTransient()) { + builder.isTransient = isTransient; + } + if (hasInitialPersistentHeartbeatDelayMs()) { + builder.initialPersistentHeartbeatDelayMs = initialPersistentHeartbeatDelayMs; + } + if (hasChannelSupportsOfflineDelivery()) { + builder.channelSupportsOfflineDelivery = channelSupportsOfflineDelivery; + } + if (hasOfflineHeartbeatThresholdMs()) { + builder.offlineHeartbeatThresholdMs = offlineHeartbeatThresholdMs; + } + if (hasAllowSuppression()) { + builder.allowSuppression = allowSuppression; + } + return builder; + } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ClientConfigP)) { return false; } + ClientConfigP other = (ClientConfigP) obj; + return __hazzerBits == other.__hazzerBits + && equals(version, other.version) + && (!hasNetworkTimeoutDelayMs() || networkTimeoutDelayMs == other.networkTimeoutDelayMs) + && (!hasWriteRetryDelayMs() || writeRetryDelayMs == other.writeRetryDelayMs) + && (!hasHeartbeatIntervalMs() || heartbeatIntervalMs == other.heartbeatIntervalMs) + && (!hasPerfCounterDelayMs() || perfCounterDelayMs == other.perfCounterDelayMs) + && (!hasMaxExponentialBackoffFactor() || maxExponentialBackoffFactor == other.maxExponentialBackoffFactor) + && (!hasSmearPercent() || smearPercent == other.smearPercent) + && (!hasIsTransient() || isTransient == other.isTransient) + && (!hasInitialPersistentHeartbeatDelayMs() || initialPersistentHeartbeatDelayMs == other.initialPersistentHeartbeatDelayMs) + && equals(protocolHandlerConfig, other.protocolHandlerConfig) + && (!hasChannelSupportsOfflineDelivery() || channelSupportsOfflineDelivery == other.channelSupportsOfflineDelivery) + && (!hasOfflineHeartbeatThresholdMs() || offlineHeartbeatThresholdMs == other.offlineHeartbeatThresholdMs) + && (!hasAllowSuppression() || allowSuppression == other.allowSuppression); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + version.hashCode(); + if (hasNetworkTimeoutDelayMs()) { + result = result * 31 + hash(networkTimeoutDelayMs); + } + if (hasWriteRetryDelayMs()) { + result = result * 31 + hash(writeRetryDelayMs); + } + if (hasHeartbeatIntervalMs()) { + result = result * 31 + hash(heartbeatIntervalMs); + } + if (hasPerfCounterDelayMs()) { + result = result * 31 + hash(perfCounterDelayMs); + } + if (hasMaxExponentialBackoffFactor()) { + result = result * 31 + hash(maxExponentialBackoffFactor); + } + if (hasSmearPercent()) { + result = result * 31 + hash(smearPercent); + } + if (hasIsTransient()) { + result = result * 31 + hash(isTransient); + } + if (hasInitialPersistentHeartbeatDelayMs()) { + result = result * 31 + hash(initialPersistentHeartbeatDelayMs); + } + result = result * 31 + protocolHandlerConfig.hashCode(); + if (hasChannelSupportsOfflineDelivery()) { + result = result * 31 + hash(channelSupportsOfflineDelivery); + } + if (hasOfflineHeartbeatThresholdMs()) { + result = result * 31 + hash(offlineHeartbeatThresholdMs); + } + if (hasAllowSuppression()) { + result = result * 31 + hash(allowSuppression); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ClientConfigP:"); + builder.append(" version=").append(version); + if (hasNetworkTimeoutDelayMs()) { + builder.append(" network_timeout_delay_ms=").append(networkTimeoutDelayMs); + } + if (hasWriteRetryDelayMs()) { + builder.append(" write_retry_delay_ms=").append(writeRetryDelayMs); + } + if (hasHeartbeatIntervalMs()) { + builder.append(" heartbeat_interval_ms=").append(heartbeatIntervalMs); + } + if (hasPerfCounterDelayMs()) { + builder.append(" perf_counter_delay_ms=").append(perfCounterDelayMs); + } + if (hasMaxExponentialBackoffFactor()) { + builder.append(" max_exponential_backoff_factor=").append(maxExponentialBackoffFactor); + } + if (hasSmearPercent()) { + builder.append(" smear_percent=").append(smearPercent); + } + if (hasIsTransient()) { + builder.append(" is_transient=").append(isTransient); + } + if (hasInitialPersistentHeartbeatDelayMs()) { + builder.append(" initial_persistent_heartbeat_delay_ms=").append(initialPersistentHeartbeatDelayMs); + } + builder.append(" protocol_handler_config=").append(protocolHandlerConfig); + if (hasChannelSupportsOfflineDelivery()) { + builder.append(" channel_supports_offline_delivery=").append(channelSupportsOfflineDelivery); + } + if (hasOfflineHeartbeatThresholdMs()) { + builder.append(" offline_heartbeat_threshold_ms=").append(offlineHeartbeatThresholdMs); + } + if (hasAllowSuppression()) { + builder.append(" allow_suppression=").append(allowSuppression); + } + builder.append('>'); + } + + public static ClientConfigP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ClientConfigP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ClientConfigP fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ClientConfigP message) { + if (message == null) { return null; } + return new ClientConfigP(com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version.fromMessageNano(message.version), + message.networkTimeoutDelayMs, + message.writeRetryDelayMs, + message.heartbeatIntervalMs, + message.perfCounterDelayMs, + message.maxExponentialBackoffFactor, + message.smearPercent, + message.isTransient, + message.initialPersistentHeartbeatDelayMs, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.ProtocolHandlerConfigP.fromMessageNano(message.protocolHandlerConfig), + message.channelSupportsOfflineDelivery, + message.offlineHeartbeatThresholdMs, + message.allowSuppression); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ClientConfigP toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ClientConfigP msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ClientConfigP(); + msg.version = version.toMessageNano(); + msg.networkTimeoutDelayMs = hasNetworkTimeoutDelayMs() ? networkTimeoutDelayMs : null; + msg.writeRetryDelayMs = hasWriteRetryDelayMs() ? writeRetryDelayMs : null; + msg.heartbeatIntervalMs = hasHeartbeatIntervalMs() ? heartbeatIntervalMs : null; + msg.perfCounterDelayMs = hasPerfCounterDelayMs() ? perfCounterDelayMs : null; + msg.maxExponentialBackoffFactor = hasMaxExponentialBackoffFactor() ? maxExponentialBackoffFactor : null; + msg.smearPercent = hasSmearPercent() ? smearPercent : null; + msg.isTransient = hasIsTransient() ? isTransient : null; + msg.initialPersistentHeartbeatDelayMs = hasInitialPersistentHeartbeatDelayMs() ? initialPersistentHeartbeatDelayMs : null; + msg.protocolHandlerConfig = protocolHandlerConfig.toMessageNano(); + msg.channelSupportsOfflineDelivery = hasChannelSupportsOfflineDelivery() ? channelSupportsOfflineDelivery : null; + msg.offlineHeartbeatThresholdMs = hasOfflineHeartbeatThresholdMs() ? offlineHeartbeatThresholdMs : null; + msg.allowSuppression = hasAllowSuppression() ? allowSuppression : null; + return msg; + } + } + + public static final class ConfigChangeMessage extends ProtoWrapper { + public static ConfigChangeMessage create(Long nextMessageDelayMs) { + return new ConfigChangeMessage(nextMessageDelayMs); + } + + public static final ConfigChangeMessage DEFAULT_INSTANCE = new ConfigChangeMessage(null); + + private final long __hazzerBits; + private final long nextMessageDelayMs; + + private ConfigChangeMessage(Long nextMessageDelayMs) throws ValidationArgumentException { + int hazzerBits = 0; + if (nextMessageDelayMs != null) { + hazzerBits |= 0x1; + positive("next_message_delay_ms", nextMessageDelayMs); + this.nextMessageDelayMs = nextMessageDelayMs; + } else { + this.nextMessageDelayMs = 0; + } + this.__hazzerBits = hazzerBits; + } + + public long getNextMessageDelayMs() { return nextMessageDelayMs; } + public boolean hasNextMessageDelayMs() { return (0x1 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ConfigChangeMessage)) { return false; } + ConfigChangeMessage other = (ConfigChangeMessage) obj; + return __hazzerBits == other.__hazzerBits + && (!hasNextMessageDelayMs() || nextMessageDelayMs == other.nextMessageDelayMs); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasNextMessageDelayMs()) { + result = result * 31 + hash(nextMessageDelayMs); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ConfigChangeMessage:"); + if (hasNextMessageDelayMs()) { + builder.append(" next_message_delay_ms=").append(nextMessageDelayMs); + } + builder.append('>'); + } + + public static ConfigChangeMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ConfigChangeMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ConfigChangeMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ConfigChangeMessage message) { + if (message == null) { return null; } + return new ConfigChangeMessage(message.nextMessageDelayMs); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ConfigChangeMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ConfigChangeMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ConfigChangeMessage(); + msg.nextMessageDelayMs = hasNextMessageDelayMs() ? nextMessageDelayMs : null; + return msg; + } + } + + public static final class ErrorMessage extends ProtoWrapper { + public interface Code { + public static final int AUTH_FAILURE = 1; + public static final int UNKNOWN_FAILURE = 10000; + } + + public static ErrorMessage create(int code, + String description) { + return new ErrorMessage(code, description); + } + + private final int code; + private final String description; + + private ErrorMessage(Integer code, + String description) throws ValidationArgumentException { + required("code", code); + this.code = code; + required("description", description); + this.description = description; + } + + public int getCode() { return code; } + + public String getDescription() { return description; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ErrorMessage)) { return false; } + ErrorMessage other = (ErrorMessage) obj; + return code == other.code + && equals(description, other.description); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + hash(code); + result = result * 31 + description.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ErrorMessage:"); + builder.append(" code=").append(code); + builder.append(" description=").append(description); + builder.append('>'); + } + + public static ErrorMessage parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoClientProtocol.ErrorMessage(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ErrorMessage fromMessageNano(com.google.protos.ipc.invalidation.NanoClientProtocol.ErrorMessage message) { + if (message == null) { return null; } + return new ErrorMessage(message.code, + message.description); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoClientProtocol.ErrorMessage toMessageNano() { + com.google.protos.ipc.invalidation.NanoClientProtocol.ErrorMessage msg = new com.google.protos.ipc.invalidation.NanoClientProtocol.ErrorMessage(); + msg.code = code; + msg.description = description; + return msg; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/CommonProtos.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/CommonProtos.java new file mode 100644 index 0000000..456aa6e --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/CommonProtos.java @@ -0,0 +1,120 @@ +/* + * 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.proto; + +import com.google.ipc.invalidation.ticl.proto.AndroidChannel.AndroidEndpointId; +import com.google.ipc.invalidation.ticl.proto.ChannelCommon.NetworkEndpointId; +import com.google.ipc.invalidation.ticl.proto.ChannelCommon.NetworkEndpointId.NetworkAddress; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ClientVersion; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationStatus; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.ServerHeader; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.StatusP; +import com.google.ipc.invalidation.ticl.proto.ClientProtocol.Version; +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.Preconditions; + + +/** Utilities for creating protocol buffer wrappers. */ +public class CommonProtos { + + public static boolean isAllObjectId(ObjectIdP objectId) { + return ClientConstants.ALL_OBJECT_ID.equals(objectId); + } + + /** Returns true iff status corresponds to permanent failure. */ + public static boolean isPermanentFailure(StatusP status) { + return status.getCode() == StatusP.Code.PERMANENT_FAILURE; + } + + /** Returns true iff status corresponds to success. */ + public static boolean isSuccess(StatusP status) { + return status.getCode() == StatusP.Code.SUCCESS; + } + + /** Returns true iff status corresponds to transient failure. */ + public static boolean isTransientFailure(StatusP status) { + return status.getCode() == StatusP.Code.TRANSIENT_FAILURE; + } + + /** + * Constructs a network endpoint id for an Android client with the given {@code registrationId}, + * {@code clientKey}, and {@code packageName}. + */ + public static NetworkEndpointId newAndroidEndpointId(String registrationId, String clientKey, + String packageName, Version channelVersion) { + Preconditions.checkNotNull(registrationId, "Null registration id"); + Preconditions.checkNotNull(clientKey, "Null client key"); + Preconditions.checkNotNull(packageName, "Null package name"); + Preconditions.checkNotNull(channelVersion, "Null channel version"); + + AndroidEndpointId endpoint = AndroidEndpointId.create(registrationId, clientKey, + /* senderId */ null, channelVersion, packageName); + return NetworkEndpointId.create(NetworkAddress.ANDROID, new Bytes(endpoint.toByteArray()), + null); + } + + public static ClientVersion newClientVersion(String platform, String language, + String applicationInfo) { + return ClientVersion.create(ClientConstants.CLIENT_VERSION_VALUE, platform, language, + applicationInfo); + } + + public static StatusP newFailureStatus(boolean isTransient, String description) { + return StatusP.create( + isTransient ? StatusP.Code.TRANSIENT_FAILURE : StatusP.Code.PERMANENT_FAILURE, description); + } + + + public static InvalidationP newInvalidationP(ObjectIdP objectId, long version, + boolean isTrickleRestart, byte[] payload) { + return InvalidationP.create(objectId, /* isKnownVersion */ true, + version, Bytes.fromByteArray(payload), /* bridgeArrivalTimeMsDeprecated */ null, + isTrickleRestart); + } + + public static InvalidationP newInvalidationPForUnknownVersion(ObjectIdP oid, + long sequenceNumber) { + return InvalidationP.create(oid, /* isKnownVersion */ false, sequenceNumber, /* payload */ null, + /* bridgeArrivalTimeMsDeprecated */ null, /* isTrickleRestart */ true); + } + + public static RegistrationP newRegistrationP(ObjectIdP oid, boolean isReg) { + return RegistrationP.create(oid, + isReg ? RegistrationP.OpType.REGISTER : RegistrationP.OpType.UNREGISTER); + } + + public static ServerHeader newServerHeader(byte[] clientToken, long currentTimeMs, + RegistrationSummary registrationSummary, String messageId) { + return ServerHeader.create(ClientConstants.PROTOCOL_VERSION, new Bytes(clientToken), + registrationSummary, currentTimeMs, messageId); + } + + public static StatusP newSuccessStatus() { + return StatusP.create(StatusP.Code.SUCCESS, null); + } + + public static RegistrationStatus newTransientFailureRegistrationStatus(RegistrationP registration, + String description) { + return RegistrationStatus.create(registration, newFailureStatus(true, description)); + } + + // Prevent instantiation. + private CommonProtos() {} +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/JavaClient.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/JavaClient.java new file mode 100644 index 0000000..fd99c90 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/JavaClient.java @@ -0,0 +1,1076 @@ +/* + * 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. + */ +// Generated by j/c/g/ipc/invalidation/common/proto_wrapper_generator +package com.google.ipc.invalidation.ticl.proto; + +import com.google.ipc.invalidation.util.Bytes; +import com.google.ipc.invalidation.util.ProtoWrapper; +import com.google.ipc.invalidation.util.ProtoWrapper.ValidationException; +import com.google.ipc.invalidation.util.TextBuilder; +import com.google.protobuf.nano.MessageNano; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +public interface JavaClient { + + public static final class BatcherState extends ProtoWrapper { + public static BatcherState create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registration, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> unregistration, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> acknowledgement, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> registrationSubtree, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage initializeMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage infoMessage) { + return new BatcherState(registration, unregistration, acknowledgement, registrationSubtree, initializeMessage, infoMessage); + } + + public static final BatcherState DEFAULT_INSTANCE = new BatcherState(null, null, null, null, null, null); + + private final long __hazzerBits; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registration; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> unregistration; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> acknowledgement; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> registrationSubtree; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage initializeMessage; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage infoMessage; + + private BatcherState(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registration, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> unregistration, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> acknowledgement, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> registrationSubtree, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage initializeMessage, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage infoMessage) { + int hazzerBits = 0; + this.registration = optional("registration", registration); + this.unregistration = optional("unregistration", unregistration); + this.acknowledgement = optional("acknowledgement", acknowledgement); + this.registrationSubtree = optional("registration_subtree", registrationSubtree); + this.initializeMessage = initializeMessage; + if (infoMessage != null) { + hazzerBits |= 0x1; + this.infoMessage = infoMessage; + } else { + this.infoMessage = com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage.DEFAULT_INSTANCE; + } + this.__hazzerBits = hazzerBits; + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> getRegistration() { return registration; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> getUnregistration() { return unregistration; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> getAcknowledgement() { return acknowledgement; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> getRegistrationSubtree() { return registrationSubtree; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage getNullableInitializeMessage() { return initializeMessage; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage getInfoMessage() { return infoMessage; } + public boolean hasInfoMessage() { return (0x1 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof BatcherState)) { return false; } + BatcherState other = (BatcherState) obj; + return __hazzerBits == other.__hazzerBits + && equals(registration, other.registration) + && equals(unregistration, other.unregistration) + && equals(acknowledgement, other.acknowledgement) + && equals(registrationSubtree, other.registrationSubtree) + && equals(initializeMessage, other.initializeMessage) + && (!hasInfoMessage() || equals(infoMessage, other.infoMessage)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + result = result * 31 + registration.hashCode(); + result = result * 31 + unregistration.hashCode(); + result = result * 31 + acknowledgement.hashCode(); + result = result * 31 + registrationSubtree.hashCode(); + if (initializeMessage != null) { + result = result * 31 + initializeMessage.hashCode(); + } + if (hasInfoMessage()) { + result = result * 31 + infoMessage.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<BatcherState:"); + builder.append(" registration=[").append(registration).append(']'); + builder.append(" unregistration=[").append(unregistration).append(']'); + builder.append(" acknowledgement=[").append(acknowledgement).append(']'); + builder.append(" registration_subtree=[").append(registrationSubtree).append(']'); + if (initializeMessage != null) { + builder.append(" initialize_message=").append(initializeMessage); + } + if (hasInfoMessage()) { + builder.append(" info_message=").append(infoMessage); + } + builder.append('>'); + } + + public static BatcherState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoJavaClient.BatcherState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static BatcherState fromMessageNano(com.google.protos.ipc.invalidation.NanoJavaClient.BatcherState message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registration = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP>(message.registration.length); + for (int i = 0; i < message.registration.length; i++) { + registration.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.registration[i])); + } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> unregistration = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP>(message.unregistration.length); + for (int i = 0; i < message.unregistration.length; i++) { + unregistration.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.unregistration[i])); + } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP> acknowledgement = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP>(message.acknowledgement.length); + for (int i = 0; i < message.acknowledgement.length; i++) { + acknowledgement.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.InvalidationP.fromMessageNano(message.acknowledgement[i])); + } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree> registrationSubtree = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree>(message.registrationSubtree.length); + for (int i = 0; i < message.registrationSubtree.length; i++) { + registrationSubtree.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSubtree.fromMessageNano(message.registrationSubtree[i])); + } + return new BatcherState(registration, + unregistration, + acknowledgement, + registrationSubtree, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InitializeMessage.fromMessageNano(message.initializeMessage), + com.google.ipc.invalidation.ticl.proto.ClientProtocol.InfoMessage.fromMessageNano(message.infoMessage)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoJavaClient.BatcherState toMessageNano() { + com.google.protos.ipc.invalidation.NanoJavaClient.BatcherState msg = new com.google.protos.ipc.invalidation.NanoJavaClient.BatcherState(); + msg.registration = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP[registration.size()]; + for (int i = 0; i < msg.registration.length; i++) { + msg.registration[i] = registration.get(i).toMessageNano(); + } + msg.unregistration = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP[unregistration.size()]; + for (int i = 0; i < msg.unregistration.length; i++) { + msg.unregistration[i] = unregistration.get(i).toMessageNano(); + } + msg.acknowledgement = new com.google.protos.ipc.invalidation.NanoClientProtocol.InvalidationP[acknowledgement.size()]; + for (int i = 0; i < msg.acknowledgement.length; i++) { + msg.acknowledgement[i] = acknowledgement.get(i).toMessageNano(); + } + msg.registrationSubtree = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationSubtree[registrationSubtree.size()]; + for (int i = 0; i < msg.registrationSubtree.length; i++) { + msg.registrationSubtree[i] = registrationSubtree.get(i).toMessageNano(); + } + msg.initializeMessage = this.initializeMessage != null ? initializeMessage.toMessageNano() : null; + msg.infoMessage = hasInfoMessage() ? infoMessage.toMessageNano() : null; + return msg; + } + } + + public static final class ProtocolHandlerState extends ProtoWrapper { + public static ProtocolHandlerState create(Integer messageId, + Long lastKnownServerTimeMs, + Long nextMessageSendTimeMs, + com.google.ipc.invalidation.ticl.proto.JavaClient.BatcherState batcherState) { + return new ProtocolHandlerState(messageId, lastKnownServerTimeMs, nextMessageSendTimeMs, batcherState); + } + + public static final ProtocolHandlerState DEFAULT_INSTANCE = new ProtocolHandlerState(null, null, null, null); + + private final long __hazzerBits; + private final int messageId; + private final long lastKnownServerTimeMs; + private final long nextMessageSendTimeMs; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.BatcherState batcherState; + + private ProtocolHandlerState(Integer messageId, + Long lastKnownServerTimeMs, + Long nextMessageSendTimeMs, + com.google.ipc.invalidation.ticl.proto.JavaClient.BatcherState batcherState) { + int hazzerBits = 0; + if (messageId != null) { + hazzerBits |= 0x1; + this.messageId = messageId; + } else { + this.messageId = 0; + } + if (lastKnownServerTimeMs != null) { + hazzerBits |= 0x2; + this.lastKnownServerTimeMs = lastKnownServerTimeMs; + } else { + this.lastKnownServerTimeMs = 0; + } + if (nextMessageSendTimeMs != null) { + hazzerBits |= 0x4; + this.nextMessageSendTimeMs = nextMessageSendTimeMs; + } else { + this.nextMessageSendTimeMs = 0; + } + if (batcherState != null) { + hazzerBits |= 0x8; + this.batcherState = batcherState; + } else { + this.batcherState = com.google.ipc.invalidation.ticl.proto.JavaClient.BatcherState.DEFAULT_INSTANCE; + } + this.__hazzerBits = hazzerBits; + } + + public int getMessageId() { return messageId; } + public boolean hasMessageId() { return (0x1 & __hazzerBits) != 0; } + + public long getLastKnownServerTimeMs() { return lastKnownServerTimeMs; } + public boolean hasLastKnownServerTimeMs() { return (0x2 & __hazzerBits) != 0; } + + public long getNextMessageSendTimeMs() { return nextMessageSendTimeMs; } + public boolean hasNextMessageSendTimeMs() { return (0x4 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.BatcherState getBatcherState() { return batcherState; } + public boolean hasBatcherState() { return (0x8 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof ProtocolHandlerState)) { return false; } + ProtocolHandlerState other = (ProtocolHandlerState) obj; + return __hazzerBits == other.__hazzerBits + && (!hasMessageId() || messageId == other.messageId) + && (!hasLastKnownServerTimeMs() || lastKnownServerTimeMs == other.lastKnownServerTimeMs) + && (!hasNextMessageSendTimeMs() || nextMessageSendTimeMs == other.nextMessageSendTimeMs) + && (!hasBatcherState() || equals(batcherState, other.batcherState)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasMessageId()) { + result = result * 31 + hash(messageId); + } + if (hasLastKnownServerTimeMs()) { + result = result * 31 + hash(lastKnownServerTimeMs); + } + if (hasNextMessageSendTimeMs()) { + result = result * 31 + hash(nextMessageSendTimeMs); + } + if (hasBatcherState()) { + result = result * 31 + batcherState.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<ProtocolHandlerState:"); + if (hasMessageId()) { + builder.append(" message_id=").append(messageId); + } + if (hasLastKnownServerTimeMs()) { + builder.append(" last_known_server_time_ms=").append(lastKnownServerTimeMs); + } + if (hasNextMessageSendTimeMs()) { + builder.append(" next_message_send_time_ms=").append(nextMessageSendTimeMs); + } + if (hasBatcherState()) { + builder.append(" batcher_state=").append(batcherState); + } + builder.append('>'); + } + + public static ProtocolHandlerState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoJavaClient.ProtocolHandlerState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static ProtocolHandlerState fromMessageNano(com.google.protos.ipc.invalidation.NanoJavaClient.ProtocolHandlerState message) { + if (message == null) { return null; } + return new ProtocolHandlerState(message.messageId, + message.lastKnownServerTimeMs, + message.nextMessageSendTimeMs, + com.google.ipc.invalidation.ticl.proto.JavaClient.BatcherState.fromMessageNano(message.batcherState)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoJavaClient.ProtocolHandlerState toMessageNano() { + com.google.protos.ipc.invalidation.NanoJavaClient.ProtocolHandlerState msg = new com.google.protos.ipc.invalidation.NanoJavaClient.ProtocolHandlerState(); + msg.messageId = hasMessageId() ? messageId : null; + msg.lastKnownServerTimeMs = hasLastKnownServerTimeMs() ? lastKnownServerTimeMs : null; + msg.nextMessageSendTimeMs = hasNextMessageSendTimeMs() ? nextMessageSendTimeMs : null; + msg.batcherState = hasBatcherState() ? batcherState.toMessageNano() : null; + return msg; + } + } + + public static final class RegistrationManagerStateP extends ProtoWrapper { + public static RegistrationManagerStateP create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registrations, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary lastKnownServerSummary, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> pendingOperations) { + return new RegistrationManagerStateP(registrations, lastKnownServerSummary, pendingOperations); + } + + public static final RegistrationManagerStateP DEFAULT_INSTANCE = new RegistrationManagerStateP(null, null, null); + + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registrations; + private final com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary lastKnownServerSummary; + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> pendingOperations; + + private RegistrationManagerStateP(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registrations, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary lastKnownServerSummary, + Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> pendingOperations) { + this.registrations = optional("registrations", registrations); + this.lastKnownServerSummary = lastKnownServerSummary; + this.pendingOperations = optional("pending_operations", pendingOperations); + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> getRegistrations() { return registrations; } + + public com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary getNullableLastKnownServerSummary() { return lastKnownServerSummary; } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> getPendingOperations() { return pendingOperations; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RegistrationManagerStateP)) { return false; } + RegistrationManagerStateP other = (RegistrationManagerStateP) obj; + return equals(registrations, other.registrations) + && equals(lastKnownServerSummary, other.lastKnownServerSummary) + && equals(pendingOperations, other.pendingOperations); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + registrations.hashCode(); + if (lastKnownServerSummary != null) { + result = result * 31 + lastKnownServerSummary.hashCode(); + } + result = result * 31 + pendingOperations.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RegistrationManagerStateP:"); + builder.append(" registrations=[").append(registrations).append(']'); + if (lastKnownServerSummary != null) { + builder.append(" last_known_server_summary=").append(lastKnownServerSummary); + } + builder.append(" pending_operations=[").append(pendingOperations).append(']'); + builder.append('>'); + } + + public static RegistrationManagerStateP parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoJavaClient.RegistrationManagerStateP(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RegistrationManagerStateP fromMessageNano(com.google.protos.ipc.invalidation.NanoJavaClient.RegistrationManagerStateP message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP> registrations = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP>(message.registrations.length); + for (int i = 0; i < message.registrations.length; i++) { + registrations.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.ObjectIdP.fromMessageNano(message.registrations[i])); + } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP> pendingOperations = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP>(message.pendingOperations.length); + for (int i = 0; i < message.pendingOperations.length; i++) { + pendingOperations.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationP.fromMessageNano(message.pendingOperations[i])); + } + return new RegistrationManagerStateP(registrations, + com.google.ipc.invalidation.ticl.proto.ClientProtocol.RegistrationSummary.fromMessageNano(message.lastKnownServerSummary), + pendingOperations); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoJavaClient.RegistrationManagerStateP toMessageNano() { + com.google.protos.ipc.invalidation.NanoJavaClient.RegistrationManagerStateP msg = new com.google.protos.ipc.invalidation.NanoJavaClient.RegistrationManagerStateP(); + msg.registrations = new com.google.protos.ipc.invalidation.NanoClientProtocol.ObjectIdP[registrations.size()]; + for (int i = 0; i < msg.registrations.length; i++) { + msg.registrations[i] = registrations.get(i).toMessageNano(); + } + msg.lastKnownServerSummary = this.lastKnownServerSummary != null ? lastKnownServerSummary.toMessageNano() : null; + msg.pendingOperations = new com.google.protos.ipc.invalidation.NanoClientProtocol.RegistrationP[pendingOperations.size()]; + for (int i = 0; i < msg.pendingOperations.length; i++) { + msg.pendingOperations[i] = pendingOperations.get(i).toMessageNano(); + } + return msg; + } + } + + public static final class RecurringTaskState extends ProtoWrapper { + public static RecurringTaskState create(Integer initialDelayMs, + Integer timeoutDelayMs, + Boolean scheduled, + com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState backoffState) { + return new RecurringTaskState(initialDelayMs, timeoutDelayMs, scheduled, backoffState); + } + + public static final RecurringTaskState DEFAULT_INSTANCE = new RecurringTaskState(null, null, null, null); + + private final long __hazzerBits; + private final int initialDelayMs; + private final int timeoutDelayMs; + private final boolean scheduled; + private final com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState backoffState; + + private RecurringTaskState(Integer initialDelayMs, + Integer timeoutDelayMs, + Boolean scheduled, + com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState backoffState) { + int hazzerBits = 0; + if (initialDelayMs != null) { + hazzerBits |= 0x1; + this.initialDelayMs = initialDelayMs; + } else { + this.initialDelayMs = 0; + } + if (timeoutDelayMs != null) { + hazzerBits |= 0x2; + this.timeoutDelayMs = timeoutDelayMs; + } else { + this.timeoutDelayMs = 0; + } + if (scheduled != null) { + hazzerBits |= 0x4; + this.scheduled = scheduled; + } else { + this.scheduled = false; + } + if (backoffState != null) { + hazzerBits |= 0x8; + this.backoffState = backoffState; + } else { + this.backoffState = com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState.DEFAULT_INSTANCE; + } + this.__hazzerBits = hazzerBits; + } + + public int getInitialDelayMs() { return initialDelayMs; } + public boolean hasInitialDelayMs() { return (0x1 & __hazzerBits) != 0; } + + public int getTimeoutDelayMs() { return timeoutDelayMs; } + public boolean hasTimeoutDelayMs() { return (0x2 & __hazzerBits) != 0; } + + public boolean getScheduled() { return scheduled; } + public boolean hasScheduled() { return (0x4 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState getBackoffState() { return backoffState; } + public boolean hasBackoffState() { return (0x8 & __hazzerBits) != 0; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof RecurringTaskState)) { return false; } + RecurringTaskState other = (RecurringTaskState) obj; + return __hazzerBits == other.__hazzerBits + && (!hasInitialDelayMs() || initialDelayMs == other.initialDelayMs) + && (!hasTimeoutDelayMs() || timeoutDelayMs == other.timeoutDelayMs) + && (!hasScheduled() || scheduled == other.scheduled) + && (!hasBackoffState() || equals(backoffState, other.backoffState)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasInitialDelayMs()) { + result = result * 31 + hash(initialDelayMs); + } + if (hasTimeoutDelayMs()) { + result = result * 31 + hash(timeoutDelayMs); + } + if (hasScheduled()) { + result = result * 31 + hash(scheduled); + } + if (hasBackoffState()) { + result = result * 31 + backoffState.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<RecurringTaskState:"); + if (hasInitialDelayMs()) { + builder.append(" initial_delay_ms=").append(initialDelayMs); + } + if (hasTimeoutDelayMs()) { + builder.append(" timeout_delay_ms=").append(timeoutDelayMs); + } + if (hasScheduled()) { + builder.append(" scheduled=").append(scheduled); + } + if (hasBackoffState()) { + builder.append(" backoff_state=").append(backoffState); + } + builder.append('>'); + } + + public static RecurringTaskState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoJavaClient.RecurringTaskState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static RecurringTaskState fromMessageNano(com.google.protos.ipc.invalidation.NanoJavaClient.RecurringTaskState message) { + if (message == null) { return null; } + return new RecurringTaskState(message.initialDelayMs, + message.timeoutDelayMs, + message.scheduled, + com.google.ipc.invalidation.ticl.proto.Client.ExponentialBackoffState.fromMessageNano(message.backoffState)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoJavaClient.RecurringTaskState toMessageNano() { + com.google.protos.ipc.invalidation.NanoJavaClient.RecurringTaskState msg = new com.google.protos.ipc.invalidation.NanoJavaClient.RecurringTaskState(); + msg.initialDelayMs = hasInitialDelayMs() ? initialDelayMs : null; + msg.timeoutDelayMs = hasTimeoutDelayMs() ? timeoutDelayMs : null; + msg.scheduled = hasScheduled() ? scheduled : null; + msg.backoffState = hasBackoffState() ? backoffState.toMessageNano() : null; + return msg; + } + } + + public static final class StatisticsState extends ProtoWrapper { + public static StatisticsState create(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> counter) { + return new StatisticsState(counter); + } + + public static final StatisticsState DEFAULT_INSTANCE = new StatisticsState(null); + + private final List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> counter; + + private StatisticsState(Collection<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> counter) { + this.counter = optional("counter", counter); + } + + public List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> getCounter() { return counter; } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof StatisticsState)) { return false; } + StatisticsState other = (StatisticsState) obj; + return equals(counter, other.counter); + } + + @Override protected int computeHashCode() { + int result = 1; + result = result * 31 + counter.hashCode(); + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<StatisticsState:"); + builder.append(" counter=[").append(counter).append(']'); + builder.append('>'); + } + + public static StatisticsState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoJavaClient.StatisticsState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static StatisticsState fromMessageNano(com.google.protos.ipc.invalidation.NanoJavaClient.StatisticsState message) { + if (message == null) { return null; } + List<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord> counter = new ArrayList<com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord>(message.counter.length); + for (int i = 0; i < message.counter.length; i++) { + counter.add(com.google.ipc.invalidation.ticl.proto.ClientProtocol.PropertyRecord.fromMessageNano(message.counter[i])); + } + return new StatisticsState(counter); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoJavaClient.StatisticsState toMessageNano() { + com.google.protos.ipc.invalidation.NanoJavaClient.StatisticsState msg = new com.google.protos.ipc.invalidation.NanoJavaClient.StatisticsState(); + msg.counter = new com.google.protos.ipc.invalidation.NanoClientProtocol.PropertyRecord[counter.size()]; + for (int i = 0; i < msg.counter.length; i++) { + msg.counter[i] = counter.get(i).toMessageNano(); + } + return msg; + } + } + + public static final class InvalidationClientState extends ProtoWrapper { + public static final class Builder { + public com.google.ipc.invalidation.ticl.proto.Client.RunStateP runState; + public Bytes clientToken; + public Bytes nonce; + public Boolean shouldSendRegistrations; + public Long lastMessageSendTimeMs; + public Boolean isOnline; + public com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState protocolHandlerState; + public com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP registrationManagerState; + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState acquireTokenTaskState; + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState regSyncHeartbeatTaskState; + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState persistentWriteTaskState; + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState heartbeatTaskState; + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState batchingTaskState; + public com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState lastWrittenState; + public com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState statisticsState; + public Builder() { + } + + public InvalidationClientState build() { + return new InvalidationClientState(runState, clientToken, nonce, shouldSendRegistrations, lastMessageSendTimeMs, isOnline, protocolHandlerState, registrationManagerState, acquireTokenTaskState, regSyncHeartbeatTaskState, persistentWriteTaskState, heartbeatTaskState, batchingTaskState, lastWrittenState, statisticsState); + } + } + + public static InvalidationClientState create(com.google.ipc.invalidation.ticl.proto.Client.RunStateP runState, + Bytes clientToken, + Bytes nonce, + Boolean shouldSendRegistrations, + Long lastMessageSendTimeMs, + Boolean isOnline, + com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState protocolHandlerState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP registrationManagerState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState acquireTokenTaskState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState regSyncHeartbeatTaskState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState persistentWriteTaskState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState heartbeatTaskState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState batchingTaskState, + com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState lastWrittenState, + com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState statisticsState) { + return new InvalidationClientState(runState, clientToken, nonce, shouldSendRegistrations, lastMessageSendTimeMs, isOnline, protocolHandlerState, registrationManagerState, acquireTokenTaskState, regSyncHeartbeatTaskState, persistentWriteTaskState, heartbeatTaskState, batchingTaskState, lastWrittenState, statisticsState); + } + + public static final InvalidationClientState DEFAULT_INSTANCE = new InvalidationClientState(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + + private final long __hazzerBits; + private final com.google.ipc.invalidation.ticl.proto.Client.RunStateP runState; + private final Bytes clientToken; + private final Bytes nonce; + private final boolean shouldSendRegistrations; + private final long lastMessageSendTimeMs; + private final boolean isOnline; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState protocolHandlerState; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP registrationManagerState; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState acquireTokenTaskState; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState regSyncHeartbeatTaskState; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState persistentWriteTaskState; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState heartbeatTaskState; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState batchingTaskState; + private final com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState lastWrittenState; + private final com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState statisticsState; + + private InvalidationClientState(com.google.ipc.invalidation.ticl.proto.Client.RunStateP runState, + Bytes clientToken, + Bytes nonce, + Boolean shouldSendRegistrations, + Long lastMessageSendTimeMs, + Boolean isOnline, + com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState protocolHandlerState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP registrationManagerState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState acquireTokenTaskState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState regSyncHeartbeatTaskState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState persistentWriteTaskState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState heartbeatTaskState, + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState batchingTaskState, + com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState lastWrittenState, + com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState statisticsState) { + int hazzerBits = 0; + if (runState != null) { + hazzerBits |= 0x1; + this.runState = runState; + } else { + this.runState = com.google.ipc.invalidation.ticl.proto.Client.RunStateP.DEFAULT_INSTANCE; + } + if (clientToken != null) { + hazzerBits |= 0x2; + this.clientToken = clientToken; + } else { + this.clientToken = Bytes.EMPTY_BYTES; + } + if (nonce != null) { + hazzerBits |= 0x4; + this.nonce = nonce; + } else { + this.nonce = Bytes.EMPTY_BYTES; + } + if (shouldSendRegistrations != null) { + hazzerBits |= 0x8; + this.shouldSendRegistrations = shouldSendRegistrations; + } else { + this.shouldSendRegistrations = false; + } + if (lastMessageSendTimeMs != null) { + hazzerBits |= 0x10; + this.lastMessageSendTimeMs = lastMessageSendTimeMs; + } else { + this.lastMessageSendTimeMs = 0; + } + if (isOnline != null) { + hazzerBits |= 0x20; + this.isOnline = isOnline; + } else { + this.isOnline = false; + } + if (protocolHandlerState != null) { + hazzerBits |= 0x40; + this.protocolHandlerState = protocolHandlerState; + } else { + this.protocolHandlerState = com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState.DEFAULT_INSTANCE; + } + if (registrationManagerState != null) { + hazzerBits |= 0x80; + this.registrationManagerState = registrationManagerState; + } else { + this.registrationManagerState = com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP.DEFAULT_INSTANCE; + } + if (acquireTokenTaskState != null) { + hazzerBits |= 0x100; + this.acquireTokenTaskState = acquireTokenTaskState; + } else { + this.acquireTokenTaskState = com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.DEFAULT_INSTANCE; + } + if (regSyncHeartbeatTaskState != null) { + hazzerBits |= 0x200; + this.regSyncHeartbeatTaskState = regSyncHeartbeatTaskState; + } else { + this.regSyncHeartbeatTaskState = com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.DEFAULT_INSTANCE; + } + if (persistentWriteTaskState != null) { + hazzerBits |= 0x400; + this.persistentWriteTaskState = persistentWriteTaskState; + } else { + this.persistentWriteTaskState = com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.DEFAULT_INSTANCE; + } + if (heartbeatTaskState != null) { + hazzerBits |= 0x800; + this.heartbeatTaskState = heartbeatTaskState; + } else { + this.heartbeatTaskState = com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.DEFAULT_INSTANCE; + } + if (batchingTaskState != null) { + hazzerBits |= 0x1000; + this.batchingTaskState = batchingTaskState; + } else { + this.batchingTaskState = com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.DEFAULT_INSTANCE; + } + if (lastWrittenState != null) { + hazzerBits |= 0x2000; + this.lastWrittenState = lastWrittenState; + } else { + this.lastWrittenState = com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState.DEFAULT_INSTANCE; + } + if (statisticsState != null) { + hazzerBits |= 0x4000; + this.statisticsState = statisticsState; + } else { + this.statisticsState = com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState.DEFAULT_INSTANCE; + } + this.__hazzerBits = hazzerBits; + } + + public com.google.ipc.invalidation.ticl.proto.Client.RunStateP getRunState() { return runState; } + public boolean hasRunState() { return (0x1 & __hazzerBits) != 0; } + + public Bytes getClientToken() { return clientToken; } + public boolean hasClientToken() { return (0x2 & __hazzerBits) != 0; } + + public Bytes getNonce() { return nonce; } + public boolean hasNonce() { return (0x4 & __hazzerBits) != 0; } + + public boolean getShouldSendRegistrations() { return shouldSendRegistrations; } + public boolean hasShouldSendRegistrations() { return (0x8 & __hazzerBits) != 0; } + + public long getLastMessageSendTimeMs() { return lastMessageSendTimeMs; } + public boolean hasLastMessageSendTimeMs() { return (0x10 & __hazzerBits) != 0; } + + public boolean getIsOnline() { return isOnline; } + public boolean hasIsOnline() { return (0x20 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState getProtocolHandlerState() { return protocolHandlerState; } + public boolean hasProtocolHandlerState() { return (0x40 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP getRegistrationManagerState() { return registrationManagerState; } + public boolean hasRegistrationManagerState() { return (0x80 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState getAcquireTokenTaskState() { return acquireTokenTaskState; } + public boolean hasAcquireTokenTaskState() { return (0x100 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState getRegSyncHeartbeatTaskState() { return regSyncHeartbeatTaskState; } + public boolean hasRegSyncHeartbeatTaskState() { return (0x200 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState getPersistentWriteTaskState() { return persistentWriteTaskState; } + public boolean hasPersistentWriteTaskState() { return (0x400 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState getHeartbeatTaskState() { return heartbeatTaskState; } + public boolean hasHeartbeatTaskState() { return (0x800 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState getBatchingTaskState() { return batchingTaskState; } + public boolean hasBatchingTaskState() { return (0x1000 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState getLastWrittenState() { return lastWrittenState; } + public boolean hasLastWrittenState() { return (0x2000 & __hazzerBits) != 0; } + + public com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState getStatisticsState() { return statisticsState; } + public boolean hasStatisticsState() { return (0x4000 & __hazzerBits) != 0; } + + public Builder toBuilder() { + Builder builder = new Builder(); + if (hasRunState()) { + builder.runState = runState; + } + if (hasClientToken()) { + builder.clientToken = clientToken; + } + if (hasNonce()) { + builder.nonce = nonce; + } + if (hasShouldSendRegistrations()) { + builder.shouldSendRegistrations = shouldSendRegistrations; + } + if (hasLastMessageSendTimeMs()) { + builder.lastMessageSendTimeMs = lastMessageSendTimeMs; + } + if (hasIsOnline()) { + builder.isOnline = isOnline; + } + if (hasProtocolHandlerState()) { + builder.protocolHandlerState = protocolHandlerState; + } + if (hasRegistrationManagerState()) { + builder.registrationManagerState = registrationManagerState; + } + if (hasAcquireTokenTaskState()) { + builder.acquireTokenTaskState = acquireTokenTaskState; + } + if (hasRegSyncHeartbeatTaskState()) { + builder.regSyncHeartbeatTaskState = regSyncHeartbeatTaskState; + } + if (hasPersistentWriteTaskState()) { + builder.persistentWriteTaskState = persistentWriteTaskState; + } + if (hasHeartbeatTaskState()) { + builder.heartbeatTaskState = heartbeatTaskState; + } + if (hasBatchingTaskState()) { + builder.batchingTaskState = batchingTaskState; + } + if (hasLastWrittenState()) { + builder.lastWrittenState = lastWrittenState; + } + if (hasStatisticsState()) { + builder.statisticsState = statisticsState; + } + return builder; + } + + @Override public final boolean equals(Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof InvalidationClientState)) { return false; } + InvalidationClientState other = (InvalidationClientState) obj; + return __hazzerBits == other.__hazzerBits + && (!hasRunState() || equals(runState, other.runState)) + && (!hasClientToken() || equals(clientToken, other.clientToken)) + && (!hasNonce() || equals(nonce, other.nonce)) + && (!hasShouldSendRegistrations() || shouldSendRegistrations == other.shouldSendRegistrations) + && (!hasLastMessageSendTimeMs() || lastMessageSendTimeMs == other.lastMessageSendTimeMs) + && (!hasIsOnline() || isOnline == other.isOnline) + && (!hasProtocolHandlerState() || equals(protocolHandlerState, other.protocolHandlerState)) + && (!hasRegistrationManagerState() || equals(registrationManagerState, other.registrationManagerState)) + && (!hasAcquireTokenTaskState() || equals(acquireTokenTaskState, other.acquireTokenTaskState)) + && (!hasRegSyncHeartbeatTaskState() || equals(regSyncHeartbeatTaskState, other.regSyncHeartbeatTaskState)) + && (!hasPersistentWriteTaskState() || equals(persistentWriteTaskState, other.persistentWriteTaskState)) + && (!hasHeartbeatTaskState() || equals(heartbeatTaskState, other.heartbeatTaskState)) + && (!hasBatchingTaskState() || equals(batchingTaskState, other.batchingTaskState)) + && (!hasLastWrittenState() || equals(lastWrittenState, other.lastWrittenState)) + && (!hasStatisticsState() || equals(statisticsState, other.statisticsState)); + } + + @Override protected int computeHashCode() { + int result = hash(__hazzerBits); + if (hasRunState()) { + result = result * 31 + runState.hashCode(); + } + if (hasClientToken()) { + result = result * 31 + clientToken.hashCode(); + } + if (hasNonce()) { + result = result * 31 + nonce.hashCode(); + } + if (hasShouldSendRegistrations()) { + result = result * 31 + hash(shouldSendRegistrations); + } + if (hasLastMessageSendTimeMs()) { + result = result * 31 + hash(lastMessageSendTimeMs); + } + if (hasIsOnline()) { + result = result * 31 + hash(isOnline); + } + if (hasProtocolHandlerState()) { + result = result * 31 + protocolHandlerState.hashCode(); + } + if (hasRegistrationManagerState()) { + result = result * 31 + registrationManagerState.hashCode(); + } + if (hasAcquireTokenTaskState()) { + result = result * 31 + acquireTokenTaskState.hashCode(); + } + if (hasRegSyncHeartbeatTaskState()) { + result = result * 31 + regSyncHeartbeatTaskState.hashCode(); + } + if (hasPersistentWriteTaskState()) { + result = result * 31 + persistentWriteTaskState.hashCode(); + } + if (hasHeartbeatTaskState()) { + result = result * 31 + heartbeatTaskState.hashCode(); + } + if (hasBatchingTaskState()) { + result = result * 31 + batchingTaskState.hashCode(); + } + if (hasLastWrittenState()) { + result = result * 31 + lastWrittenState.hashCode(); + } + if (hasStatisticsState()) { + result = result * 31 + statisticsState.hashCode(); + } + return result; + } + + @Override public void toCompactString(TextBuilder builder) { + builder.append("<InvalidationClientState:"); + if (hasRunState()) { + builder.append(" run_state=").append(runState); + } + if (hasClientToken()) { + builder.append(" client_token=").append(clientToken); + } + if (hasNonce()) { + builder.append(" nonce=").append(nonce); + } + if (hasShouldSendRegistrations()) { + builder.append(" should_send_registrations=").append(shouldSendRegistrations); + } + if (hasLastMessageSendTimeMs()) { + builder.append(" last_message_send_time_ms=").append(lastMessageSendTimeMs); + } + if (hasIsOnline()) { + builder.append(" is_online=").append(isOnline); + } + if (hasProtocolHandlerState()) { + builder.append(" protocol_handler_state=").append(protocolHandlerState); + } + if (hasRegistrationManagerState()) { + builder.append(" registration_manager_state=").append(registrationManagerState); + } + if (hasAcquireTokenTaskState()) { + builder.append(" acquire_token_task_state=").append(acquireTokenTaskState); + } + if (hasRegSyncHeartbeatTaskState()) { + builder.append(" reg_sync_heartbeat_task_state=").append(regSyncHeartbeatTaskState); + } + if (hasPersistentWriteTaskState()) { + builder.append(" persistent_write_task_state=").append(persistentWriteTaskState); + } + if (hasHeartbeatTaskState()) { + builder.append(" heartbeat_task_state=").append(heartbeatTaskState); + } + if (hasBatchingTaskState()) { + builder.append(" batching_task_state=").append(batchingTaskState); + } + if (hasLastWrittenState()) { + builder.append(" last_written_state=").append(lastWrittenState); + } + if (hasStatisticsState()) { + builder.append(" statistics_state=").append(statisticsState); + } + builder.append('>'); + } + + public static InvalidationClientState parseFrom(byte[] data) throws ValidationException { + try { + return fromMessageNano(MessageNano.mergeFrom(new com.google.protos.ipc.invalidation.NanoJavaClient.InvalidationClientState(), data)); + } catch (InvalidProtocolBufferNanoException exception) { + throw new ValidationException(exception); + } catch (ValidationArgumentException exception) { + throw new ValidationException(exception.getMessage()); + } + } + + static InvalidationClientState fromMessageNano(com.google.protos.ipc.invalidation.NanoJavaClient.InvalidationClientState message) { + if (message == null) { return null; } + return new InvalidationClientState(com.google.ipc.invalidation.ticl.proto.Client.RunStateP.fromMessageNano(message.runState), + Bytes.fromByteArray(message.clientToken), + Bytes.fromByteArray(message.nonce), + message.shouldSendRegistrations, + message.lastMessageSendTimeMs, + message.isOnline, + com.google.ipc.invalidation.ticl.proto.JavaClient.ProtocolHandlerState.fromMessageNano(message.protocolHandlerState), + com.google.ipc.invalidation.ticl.proto.JavaClient.RegistrationManagerStateP.fromMessageNano(message.registrationManagerState), + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.fromMessageNano(message.acquireTokenTaskState), + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.fromMessageNano(message.regSyncHeartbeatTaskState), + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.fromMessageNano(message.persistentWriteTaskState), + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.fromMessageNano(message.heartbeatTaskState), + com.google.ipc.invalidation.ticl.proto.JavaClient.RecurringTaskState.fromMessageNano(message.batchingTaskState), + com.google.ipc.invalidation.ticl.proto.Client.PersistentTiclState.fromMessageNano(message.lastWrittenState), + com.google.ipc.invalidation.ticl.proto.JavaClient.StatisticsState.fromMessageNano(message.statisticsState)); + } + + public byte[] toByteArray() { + return MessageNano.toByteArray(toMessageNano()); + } + + com.google.protos.ipc.invalidation.NanoJavaClient.InvalidationClientState toMessageNano() { + com.google.protos.ipc.invalidation.NanoJavaClient.InvalidationClientState msg = new com.google.protos.ipc.invalidation.NanoJavaClient.InvalidationClientState(); + msg.runState = hasRunState() ? runState.toMessageNano() : null; + msg.clientToken = hasClientToken() ? clientToken.getByteArray() : null; + msg.nonce = hasNonce() ? nonce.getByteArray() : null; + msg.shouldSendRegistrations = hasShouldSendRegistrations() ? shouldSendRegistrations : null; + msg.lastMessageSendTimeMs = hasLastMessageSendTimeMs() ? lastMessageSendTimeMs : null; + msg.isOnline = hasIsOnline() ? isOnline : null; + msg.protocolHandlerState = hasProtocolHandlerState() ? protocolHandlerState.toMessageNano() : null; + msg.registrationManagerState = hasRegistrationManagerState() ? registrationManagerState.toMessageNano() : null; + msg.acquireTokenTaskState = hasAcquireTokenTaskState() ? acquireTokenTaskState.toMessageNano() : null; + msg.regSyncHeartbeatTaskState = hasRegSyncHeartbeatTaskState() ? regSyncHeartbeatTaskState.toMessageNano() : null; + msg.persistentWriteTaskState = hasPersistentWriteTaskState() ? persistentWriteTaskState.toMessageNano() : null; + msg.heartbeatTaskState = hasHeartbeatTaskState() ? heartbeatTaskState.toMessageNano() : null; + msg.batchingTaskState = hasBatchingTaskState() ? batchingTaskState.toMessageNano() : null; + msg.lastWrittenState = hasLastWrittenState() ? lastWrittenState.toMessageNano() : null; + msg.statisticsState = hasStatisticsState() ? statisticsState.toMessageNano() : null; + return msg; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/BaseLogger.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/BaseLogger.java new file mode 100644 index 0000000..8d5fa50 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/BaseLogger.java @@ -0,0 +1,63 @@ +/* + * 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.util; + +import java.util.logging.Level; + +/** + * A basic formatting logger interface. + * + */ +public interface BaseLogger { + /** + * Logs a message. + * + * @param level the level at which the message should be logged (e.g., {@code INFO}) + * @param template the string to log, optionally containing %s sequences + * @param args variables to substitute for %s sequences in {@code template} + */ + void log(Level level, String template, Object... args); + + /** + * Returns true iff statements at {@code level} are not being suppressed. + */ + boolean isLoggable(Level level); + + /** + * Logs a message at the SEVERE level. + * See specs of {@code #log} for the parameters. + */ + void severe(String template, Object...args); + + /** + * Logs a message at the WARNING level. + * See specs of {@code #log} for the parameters. + */ + void warning(String template, Object...args); + + /** + * Logs a message at the INFO level. + * See specs of {@code #log} for the parameters. + */ + void info(String template, Object...args); + + /** + * Logs a message at the FINE level. + * See specs of {@code #log} for the parameters. + */ + void fine(String template, Object...args); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Box.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Box.java new file mode 100644 index 0000000..0430344 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Box.java @@ -0,0 +1,59 @@ +/* + * 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.util; + +/** + * Container for a single arbitrary value. Useful when a nested callback needs + * to modify a primitive type, which is ordinarily not possible as variables + * available to nested callbacks need to be declared final. + * + * @param <T> Type of the value being boxed. + * + */ +public class Box<T> { + + /** Contents of the box. */ + private T value; + + /** Constructs a box with the given initial {@code value}. */ + public Box(T value) { + this.value = value; + } + + /** Constructs a Box with {@code null} as the value. */ + public Box() { + this.value = null; + } + + /** Constructs and returns a {@code Box} that wraps {@code objectValue}. */ + public static <T> Box<T> of(T objectValue) { + return new Box<T>(objectValue); + } + + public void set(T objectValue) { + this.value = objectValue; + } + + public T get() { + return value; + } + + @Override + public String toString() { + return (value == null) ? null : value.toString(); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Bytes.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Bytes.java new file mode 100644 index 0000000..53c0bec --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Bytes.java @@ -0,0 +1,348 @@ +/* + * 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.util; + +import com.google.ipc.invalidation.util.LazyString.LazyStringReceiver; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Locale; + + +/** + * A class that encapsulates a (fixed size) sequence of bytes and provides a + * equality (along with hashcode) method that considers two sequences to be + * equal if they have the same contents. Borrowed from protobuf's ByteString + * + */ +public class Bytes extends InternalBase implements Comparable<Bytes> { + + public static final Bytes EMPTY_BYTES = new Bytes(new byte[0]); + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + /** + * Interface accessing byte elements from {@code T}, which may be (for instance) + * {@link com.google.protobuf.ByteString ByteString} or {@code byte[]}. + */ + interface BytesAccessor<T> { + int size(T bytes); + byte get(T bytes, int index); + } + + private static final BytesAccessor<byte[]> BYTE_ARRAY_ACCESSOR = new BytesAccessor<byte[]>() { + @Override public int size(byte[] bytes) { + return bytes == null ? 0 : bytes.length; + } + + @Override public byte get(byte[] bytes, int index) { + return bytes[index]; + } + }; + + private static final LazyStringReceiver<byte[]> BYTE_ARRAY_RECEIVER = + new LazyStringReceiver<byte[]>() { + @Override public void appendToBuilder(TextBuilder builder, byte[] element) { + toCompactString(builder, element); + } + }; + + /** + * Three arrays that store the representation of each character from 0 to 255. + * The ith number's octal representation is: CHAR_OCTAL_STRINGS1[i], + * CHAR_OCTAL_STRINGS2[i], CHAR_OCTAL_STRINGS3[i] + * <p> + * E.g., if the number 128, these arrays contain 2, 0, 0 at index 128. We use + * 3 char arrays instead of an array of strings since the code path for a + * character append operation is quite a bit shorter than the append operation + * for strings. + */ + private static final char[] CHAR_OCTAL_STRINGS1 = new char[256]; + private static final char[] CHAR_OCTAL_STRINGS2 = new char[256]; + private static final char[] CHAR_OCTAL_STRINGS3 = new char[256]; + + /** The actual sequence. */ + private final byte[] bytes; + + /** Cached hash */ + private volatile int hash = 0; + + static { + // Initialize the array with the Octal string values so that we do not have + // to do String.format for every byte during runtime. + for (int i = 0; i < CHAR_OCTAL_STRINGS1.length; i++) { + String value = String.format(Locale.ROOT, "\\%03o", i); + CHAR_OCTAL_STRINGS1[i] = value.charAt(1); + CHAR_OCTAL_STRINGS2[i] = value.charAt(2); + CHAR_OCTAL_STRINGS3[i] = value.charAt(3); + } + } + + public Bytes(byte[] bytes) { + this.bytes = bytes; + } + + /** + * Creates a Bytes object with the contents of {@code array1} followed by the + * contents of {@code array2}. + */ + public Bytes(byte[] array1, byte[] array2) { + Preconditions.checkNotNull(array1); + Preconditions.checkNotNull(array2); + ByteBuffer buffer = ByteBuffer.allocate(array1.length + array2.length); + buffer.put(array1); + buffer.put(array2); + this.bytes = buffer.array(); + } + + /** + * Creates a Bytes object with the contents of {@code b1} followed by the + * contents of {@code b2}. + */ + public Bytes(Bytes b1, Bytes b2) { + this(b1.bytes, b2.bytes); + } + + public Bytes(byte b) { + this.bytes = new byte[1]; + bytes[0] = b; + } + + /** Creates a Bytes object from the given string encoded as a UTF-8 byte array. */ + public static Bytes fromUtf8Encoding(String s) { + return new Bytes(s.getBytes(UTF_8)); + } + + /** + * Gets the byte at the given index. + * + * @throws ArrayIndexOutOfBoundsException {@code index} is < 0 or >= size + */ + public byte byteAt(final int index) { + return bytes[index]; + } + + /** + * Gets the number of bytes. + */ + public int size() { + return bytes.length; + } + + /** + * Returns the internal byte array. + */ + public byte[] getByteArray() { + return bytes; + } + + /** + * Returns a new {@code Bytes} containing the given subrange of bytes [{@code from}, {@code to}). + */ + public Bytes subsequence(int from, int to) { + // Identical semantics to Arrays.copyOfRange() but implemented manually so runs on + // Froyo (JDK 1.5). + int newLength = to - from; + if (newLength < 0) { + throw new IllegalArgumentException(from + " > " + to); + } + byte[] copy = new byte[newLength]; + System.arraycopy(bytes, from, copy, 0, Math.min(bytes.length - from, newLength)); + return new Bytes(copy); + } + + @Override public boolean equals(final Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof Bytes)) { + return false; + } + + final Bytes other = (Bytes) o; + return Arrays.equals(bytes, other.bytes); + } + + @Override public int hashCode() { + int h = hash; + + // If the hash has been not computed, go through each byte and compute it. + if (h == 0) { + final byte[] thisBytes = bytes; + final int size = bytes.length; + + h = size; + for (int i = 0; i < size; i++) { + h = h * 31 + thisBytes[i]; + } + if (h == 0) { + h = 1; + } + + hash = h; + } + + return h; + } + + /** + * Returns whether these bytes are a prefix (either proper or improper) of + * {@code other}. + */ + public boolean isPrefixOf(Bytes other) { + Preconditions.checkNotNull(other); + if (size() > other.size()) { + return false; + } + for (int i = 0; i < size(); ++i) { + if (bytes[i] != other.bytes[i]) { + return false; + } + } + return true; + } + + /** + * Returns whether these bytes are a suffix (either proper or improper) of + * {@code other}. + */ + public boolean isSuffixOf(Bytes other) { + Preconditions.checkNotNull(other); + int diff = other.size() - size(); + if (diff < 0) { + return false; + } + for (int i = 0; i < size(); ++i) { + if (bytes[i] != other.bytes[i + diff]) { + return false; + } + } + return true; + } + + @Override public int compareTo(Bytes other) { + return compare(bytes, other.bytes); + } + + public static Bytes fromByteArray(byte[] bytes) { + return (bytes == null) ? null : new Bytes(bytes); + } + + /** + * Same specs as {@link #compareTo} except for the byte[] type. Null arrays are ordered before + * non-null arrays. + */ + public static int compare(byte[] first, byte[] second) { + return compare(BYTE_ARRAY_ACCESSOR, first, second); + } + + /** + * Performs lexicographic comparison of two byte sequences. Null sequences are ordered before + * non-null sequences. + */ + static <T> int compare(BytesAccessor<T> accessor, T first, T second) { + // Order null arrays before non-null arrays. + if (first == null) { + return (second == null) ? 0 : -1; + } + if (second == null) { + return 1; + } + + int minLength = Math.min(accessor.size(first), accessor.size(second)); + for (int i = 0; i < minLength; i++) { + + if (accessor.get(first, i) != accessor.get(second, i)) { + int firstByte = accessor.get(first, i) & 0xff; + int secondByte = accessor.get(second, i) & 0xff; + return firstByte - secondByte; + } + } + // At this point, either both arrays are equal length or one of the arrays has ended. + // * If the arrays are of equal length, they must be identical (else we would have + // returned the correct value above + // * If they are not of equal length, the one with the longer length is greater. + return accessor.size(first) - accessor.size(second); + } + + /** + * Renders the bytes as a string in standard bigtable ascii / octal mix compatible with bt and + * returns it. + */ + public static String toString(byte[] bytes) { + return toCompactString(new TextBuilder(), bytes).toString(); + } + + /** + * Renders the bytes as a string in standard bigtable ascii / octal mix compatible with bt and + * adds it to builder. + */ + @Override public void toCompactString(TextBuilder builder) { + toCompactString(builder, bytes); + } + + /** + * Renders the bytes as a string in standard bigtable ascii / octal mix compatible with bt and + * adds it to builder. Returns {@code builder}. + */ + public static TextBuilder toCompactString(TextBuilder builder, byte[] bytes) { + return toCompactString(BYTE_ARRAY_ACCESSOR, builder, bytes); + } + + /** + * Returns an object that lazily formats {@code bytes} when {@link Object#toString()} is called. + */ + public static Object toLazyCompactString(byte[] bytes) { + if (bytes == null || bytes.length == 0) { + return ""; + } + return LazyString.toLazyCompactString(bytes, BYTE_ARRAY_RECEIVER); + } + + /** + * Renders the bytes as a string in standard bigtable ascii / octal mix compatible with bt and + * adds it to builder. Borrowed from Bigtable's {@code Util$keyToString()}. + * Returns {@code builder}. + */ + static <T> TextBuilder toCompactString(BytesAccessor<T> accessor, TextBuilder builder, + T bytes) { + for (int i = 0; i < accessor.size(bytes); i++) { + byte c = accessor.get(bytes, i); + switch(c) { + case '\n': builder.append('\\'); builder.append('n'); break; + case '\r': builder.append('\\'); builder.append('r'); break; + case '\t': builder.append('\\'); builder.append('t'); break; + case '\"': builder.append('\\'); builder.append('"'); break; + case '\\': builder.append('\\'); builder.append('\\'); break; + default: + if ((c >= 32) && (c < 127) && c != '\'') { + builder.append((char) c); + } else { + int byteValue = c; + if (c < 0) { + byteValue = c + 256; + } + builder.append('\\'); + builder.append(CHAR_OCTAL_STRINGS1[byteValue]); + builder.append(CHAR_OCTAL_STRINGS2[byteValue]); + builder.append(CHAR_OCTAL_STRINGS3[byteValue]); + } + } + } + return builder; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/ExponentialBackoffDelayGenerator.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/ExponentialBackoffDelayGenerator.java new file mode 100644 index 0000000..a2fee7e --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/ExponentialBackoffDelayGenerator.java @@ -0,0 +1,126 @@ +/* + * 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.util; + +import java.util.Random; + +/** + * Class that generates successive intervals for random exponential backoff. Class tracks a + * "high water mark" which is doubled each time {@code getNextDelay} is called; each call to + * {@code getNextDelay} returns a value uniformly randomly distributed between 0 (inclusive) and the + * high water mark (exclusive). Note that this class does not dictate the time units for which the + * delay is computed. + * + */ +public class ExponentialBackoffDelayGenerator { + + /** Initial allowed delay time. */ + private final int initialMaxDelay; + + /** Maximum allowed delay time. */ + private final int maxDelay; + + /** Next delay time to use. */ + private int currentMaxDelay; + + /** If the first call to {@code getNextDelay} has been made after reset. */ + private boolean inRetryMode; + + private final Random random; + + /** + * Creates a generator with the given initial delay and the maximum delay (in terms of a factor of + * the initial delay). + */ + public ExponentialBackoffDelayGenerator(Random random, int initialMaxDelay, + int maxExponentialFactor) { + Preconditions.checkArgument(maxExponentialFactor > 0, "max factor must be positive"); + this.random = Preconditions.checkNotNull(random); + Preconditions.checkArgument(initialMaxDelay > 0, "initial delay must be positive"); + this.initialMaxDelay = initialMaxDelay; + this.maxDelay = initialMaxDelay * maxExponentialFactor; + Preconditions.checkState(maxDelay > 0, "max delay must be positive"); + reset(); + } + + /** + * A constructor to restore a generator from saved state. Creates a generator with the given + * initial delay and the maximum delay (in terms of a factor of the initial delay). + * + * @param currentMaxDelay saved current max delay + * @param inRetryMode saved in-retry-mode value + */ + protected ExponentialBackoffDelayGenerator(Random random, int initialMaxDelay, + int maxExponentialFactor, int currentMaxDelay, boolean inRetryMode) { + this(random, initialMaxDelay, maxExponentialFactor); + this.currentMaxDelay = currentMaxDelay; + this.inRetryMode = inRetryMode; + } + + /** Resets the exponential backoff generator to start delays at the initial delay. */ + public void reset() { + this.currentMaxDelay = initialMaxDelay; + this.inRetryMode = false; + } + + /** + * Resets the exponential backoff generator to start delays such that the specified number of + * retries have already been made. */ + public void resetWithNumRetries(int numRetries) { + Preconditions.checkArgument(numRetries >= 0); + reset(); + if (numRetries > 0) { + inRetryMode = true; + if (numRetries > Integer.SIZE) { + // Cap, otherwise Java will use the lower order 5 bits causing incorrect power of 2. + numRetries = Integer.SIZE; + } + currentMaxDelay = currentMaxDelay << (numRetries - 1); + if (currentMaxDelay <= 0 || currentMaxDelay > maxDelay) { + currentMaxDelay = maxDelay; + } + } + } + + /** Gets the next delay interval to use. */ + public int getNextDelay() { + int delay = 0; // After a reset, the delay is 0. + if (inRetryMode) { + + // Generate the delay in the range [1, currentMaxDelay]. + delay = random.nextInt(currentMaxDelay) + 1; + + // Adjust the max for the next run. + if (currentMaxDelay <= maxDelay) { // Guard against overflow. + currentMaxDelay *= 2; + if (currentMaxDelay > maxDelay) { + currentMaxDelay = maxDelay; + } + } + } + inRetryMode = true; + return delay; + } + + protected int getCurrentMaxDelay() { + return currentMaxDelay; + } + + protected boolean getInRetryMode() { + return inRetryMode; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Formatter.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Formatter.java new file mode 100644 index 0000000..147853d --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Formatter.java @@ -0,0 +1,72 @@ +/* + * 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.util; + +/** + * A set of utilities needed to format strings by the external class implementations. + * + */ +public class Formatter { + + /** + * (Borrowed from the Preconditions code). + * Substitutes each {@code %s} in {@code template} with an argument. These + * are matched by position - the first {@code %s} gets {@code args[0]}, etc. + * If there are more arguments than placeholders, the unmatched arguments will + * be appended to the end of the formatted message in square braces. + * + * @param template a non-null string containing 0 or more {@code %s} + * placeholders. + * @param args the arguments to be substituted into the message + * template. Arguments are converted to strings using + * {@link String#valueOf(Object)}. Arguments can be null. + */ + public static String format(String template, Object... args) { + template = String.valueOf(template); // null -> "null" + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder( + template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template.substring(templateStart, placeholderStart)); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template.substring(templateStart)); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + return builder.toString(); + } + + private Formatter() { // To prevent instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/InternalBase.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/InternalBase.java new file mode 100644 index 0000000..74c9cca --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/InternalBase.java @@ -0,0 +1,94 @@ +/* + * 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.util; + +/** + * {@code InternalBase} is a class from which other classes can derive that allows an efficient + * toString implementation for logging/debugging purposes for those classes. The class is abstract + * so that it is never instantiated explicitly. + * + */ +public abstract class InternalBase { + + /** + * Adds a compact representation of this object to {@code builder}. + * + * @param builder the builder in which the string representation is added + */ + public abstract void toCompactString(TextBuilder builder); + + /** + * Adds a verbose representation of this object to {@code builder}. The + * default implementation for toVerboseString is to simply call + * toCompactString. + * + * @param builder the builder in which the string representation is added + */ + public void toVerboseString(TextBuilder builder) { + toCompactString(builder); + } + + @Override + public String toString() { + TextBuilder builder = new TextBuilder(); + toCompactString(builder); + return builder.toString(); + } + + /** + * Creates a TextBuilder internally and returns a string based on the {@code + * toVerboseString} method described above. + */ + public String toVerboseString() { + TextBuilder builder = new TextBuilder(); + toVerboseString(builder); + return builder.toString(); + } + + /** + * Given a set of {@code objects}, calls {@code toCompactString} on each of + * them with the {@code builder} and separates each object's output in the + * {@code builder} with a comma. + */ + public static void toCompactStrings(TextBuilder builder, + Iterable<? extends InternalBase> objects) { + boolean first = true; + for (InternalBase object : objects) { + if (!first) { + builder.append(", "); + } + object.toCompactString(builder); + first = false; + } + } + + /** + * Given a set of {@code objects}, calls {@code toString} on each of + * them with the {@code builder} and separates each object's output in the + * {@code builder} with a comma. + */ + public static void toStrings(TextBuilder builder, Iterable<?> objects) { + boolean first = true; + for (Object object : objects) { + if (!first) { + builder.append(", "); + } + builder.append(object.toString()); + first = false; + } + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/LazyString.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/LazyString.java new file mode 100644 index 0000000..bc85c60 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/LazyString.java @@ -0,0 +1,144 @@ +/* + * 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.util; + +import java.util.Collection; + + +/** + * Utilities to enable creation of lazy strings, where the instantiation of the string is delayed + * so that, e.g., log messages that aren't printed have reduced overhead. + */ +public class LazyString { + + /** Receiver formatting objects using {@link Object#toString()}. */ + public static final LazyStringReceiver<Object> OBJECT_RECEIVER = + new LazyStringReceiver<Object>() { + @Override + public void appendToBuilder(TextBuilder builder, Object element) { + builder.append(element); + } + }; + + /** + * Receiver appending an {@code element} to the given {@code builder}. Implementations may assume + * the builder and element are not {@code null}. + */ + public interface LazyStringReceiver<T> { + void appendToBuilder(TextBuilder builder, T element); + } + + /** + * Given an {@code element} to be logged lazily, returns null if the object is null. Otherwise, + * return an object that would convert it to a string using {@code builderFunction}. I.e., this + * method will call {@code builderFunction} with a new {@link TextBuilder} and provided + * {@code element} and return the string created with it. + */ + public static <T> Object toLazyCompactString(final T element, + final LazyStringReceiver<T> builderFunction) { + if (element == null) { + return null; + } + return new Object() { + @Override public String toString() { + TextBuilder builder = new TextBuilder(); + builderFunction.appendToBuilder(builder, element); + return builder.toString(); + } + }; + } + + /** + * Returns an object that converts the given {@code elements} array into a debug string when + * {@link Object#toString} is called using + * {@link #appendElementsToBuilder(TextBuilder, Object[], LazyStringReceiver)}. + */ + public static <T> Object toLazyCompactString(final T[] elements, + final LazyStringReceiver<? super T> elementReceiver) { + if ((elements == null) || (elements.length == 0)) { + return null; + } + return new Object() { + @Override public String toString() { + return appendElementsToBuilder(new TextBuilder(), elements, elementReceiver).toString(); + } + }; + } + + /** + * Returns an object that converts the given {@code elements} collection into a debug string when + * {@link Object#toString} is called using + * {@link #appendElementsToBuilder(TextBuilder, Object[], LazyStringReceiver)}. + */ + public static <T> Object toLazyCompactString(final Collection<T> elements, + final LazyStringReceiver<? super T> elementReceiver) { + if ((elements == null) || elements.isEmpty()) { + return null; + } + return new Object() { + @Override public String toString() { + return appendElementsToBuilder(new TextBuilder(), elements, elementReceiver).toString(); + } + }; + } + + /** + * Appends {@code elements} formatted using {@code elementReceiver} to {@code builder}. Elements + * are comma-separated. + */ + public static <T> TextBuilder appendElementsToBuilder(TextBuilder builder, T[] elements, + LazyStringReceiver<? super T> elementReceiver) { + if (elements == null) { + return builder; + } + for (int i = 0; i < elements.length; i++) { + if (i != 0) { + builder.append(", "); + } + T element = elements[i]; + if (element != null) { + elementReceiver.appendToBuilder(builder, element); + } + } + return builder; + } + + /** + * Appends {@code elements} formatted using {@code elementReceiver} to {@code builder}. Elements + * are comma-separated. + */ + public static <T> TextBuilder appendElementsToBuilder(TextBuilder builder, + Iterable<T> elements, LazyStringReceiver<? super T> elementReceiver) { + if (elements == null) { + return builder; + } + boolean first = true; + for (T element : elements) { + if (first) { + first = false; + } else { + builder.append(", "); + } + if (element != null) { + elementReceiver.appendToBuilder(builder, element); + } + } + return builder; + } + + private LazyString() { // To prevent instantiation. + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Marshallable.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Marshallable.java new file mode 100644 index 0000000..5b6a3cad --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Marshallable.java @@ -0,0 +1,27 @@ +/* + * 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.util; + +/** + * Interface for classes that can marshall their state to a protocol buffer. + * + * @param <T> the type of protocol buffer returned by {@link #marshal} + */ +public interface Marshallable<T> { + /** Returns a protocol buffer containing the class state. */ + T marshal(); +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/NamedRunnable.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/NamedRunnable.java new file mode 100644 index 0000000..7a29fe0 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/NamedRunnable.java @@ -0,0 +1,43 @@ +/* + * 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.util; + +/** + * A wrapper around the {@link Runnable} interface that provides extra information (e.g., a name) + * for logging, monitoring, debugging, etc. + * + */ +public abstract class NamedRunnable implements Runnable { + + /** The name of this runnable. */ + private final String name; + + /** Constructs a named runnable with the given name. */ + public NamedRunnable(String name) { + Preconditions.checkNotNull(name, "name is null"); + this.name = name; + } + + @Override + public String toString() { + return name + ':' + super.toString(); + } + + public String getName() { + return name; + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Preconditions.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Preconditions.java new file mode 100644 index 0000000..324930e --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Preconditions.java @@ -0,0 +1,78 @@ +/* + * 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.util; + + +/** + * Precondition checkers modeled after {@link com.google.common.base.Preconditions}. Duplicated here + * to avoid the dependency on guava in Java client code. + */ +public class Preconditions { + + /** + * Throws {@link NullPointerException} if the {@code reference} argument is + * {@code null}. Otherwise, returns {@code reference}. + */ + public static <T> T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Throws {@link NullPointerException} if the {@code reference} argument is + * {@code null}. Otherwise, returns {@code reference}. + */ + public static <T> T checkNotNull(T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** Throws {@link IllegalStateException} if the given {@code expression} is {@code false}. */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /** Throws {@link IllegalStateException} if the given {@code expression} is {@code false}. */ + public static void checkState(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** Throws {@link IllegalArgumentException} if the given {@code expression} is {@code false}. */ + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** Throws {@link IllegalArgumentException} if the given {@code expression} is {@code false}. */ + public static void checkArgument(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + // Do not instantiate. + private Preconditions() { + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/ProtoWrapper.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/ProtoWrapper.java new file mode 100644 index 0000000..680b82a --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/ProtoWrapper.java @@ -0,0 +1,237 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + + +/** + * Base class for generated protobuf wrapper classes. Includes utilities for validation of proto + * fields and implements hash code memoization. + */ +public abstract class ProtoWrapper extends InternalBase { + + /** Unchecked validation exception indicating a code issue. */ + public static final class ValidationArgumentException extends IllegalArgumentException { + public ValidationArgumentException(String message) { + super(message); + } + } + + /** Checked validation exception indicating an bogus protocol buffer instance. */ + public static final class ValidationException extends Exception { + public ValidationException(String message) { + super(message); + } + + public ValidationException(Throwable cause) { + super(cause); + } + } + + /** Immutable, empty list. */ + private static final List<?> EMPTY_LIST = Collections.unmodifiableList(new ArrayList<Object>(0)); + private static final int UNINITIALIZED_HASH_CODE = -1; + private static final int NOT_UNITIALIZED_HASH_CODE = UNINITIALIZED_HASH_CODE + 1; + private int hashCode; + + @Override + public final int hashCode() { + if (hashCode == UNINITIALIZED_HASH_CODE) { + int computedHashCode = computeHashCode(); + + // If computeHashCode() happens to return UNITIALIZED_HASH_CODE, replace it with a + // different (constant but arbitrary) value so that the hash code doesn't need to be + // recomputed. + hashCode = (computedHashCode == UNINITIALIZED_HASH_CODE) ? NOT_UNITIALIZED_HASH_CODE + : computedHashCode; + } + return hashCode; + } + + /** Returns a hash code for this wrapper. */ + protected abstract int computeHashCode(); + + /** Returns an immutable, empty list with elements of type {@code T}. */ + @SuppressWarnings("unchecked") + protected static <T> List<T> emptyList() { + return (List<T>) EMPTY_LIST; + } + + /** Checks that the given field is non null. */ + protected static void required(String fieldName, Object fieldValue) { + if (fieldValue == null) { + throw new ValidationArgumentException( + String.format(Locale.ROOT, "Required field '%s' was not set", fieldName)); + } + } + + /** + * Checks that the given collection contains non-null elements. Treats {@code null} as empty. + * Returns an immutable copy of the given collection. + */ + protected static <T> List<T> optional(String fieldName, Collection<T> fieldValues) { + if ((fieldValues == null) || (fieldValues.size() == 0)) { + return emptyList(); + } + ArrayList<T> copy = new ArrayList<T>(fieldValues); + for (int i = 0; i < copy.size(); i++) { + if (copy.get(i) == null) { + throw new ValidationArgumentException(String.format(Locale.ROOT, + "Element %d of repeated field '%s' must not be null.", i, fieldName)); + } + } + return Collections.unmodifiableList(copy); + } + + /** + * Checks that the given field is non-empty. Returns an immutable copy of the given + * collection. + */ + protected static <T> List<T> required(String fieldName, Collection<T> fieldValues) { + List<T> copy = optional(fieldName, fieldValues); + if (fieldValues.isEmpty()) { + throw new ValidationArgumentException(String.format(Locale.ROOT, + "Repeated field '%s' must have at least one element", fieldName)); + } + return copy; + } + + /** Checks that the given field is non-negative. */ + protected static void nonNegative(String fieldName, int value) { + if (value < 0) { + throw new ValidationArgumentException( + String.format(Locale.ROOT, "Field '%s' must be non-negative: %d", fieldName, value)); + } + } + + /** Checks that the given field is non-negative. */ + protected static void nonNegative(String fieldName, long value) { + if (value < 0) { + throw new ValidationArgumentException( + String.format(Locale.ROOT, "Field '%s' must be non-negative: %d", fieldName, value)); + } + } + + /** Checks that the given field is positive. */ + protected static void positive(String fieldName, int value) { + if (value <= 0) { + throw new ValidationArgumentException( + String.format(Locale.ROOT, "Field '%s' must be positive: %d", fieldName, value)); + } + } + + /** Checks that the given field is positive. */ + protected static void positive(String fieldName, long value) { + if (value <= 0) { + throw new ValidationArgumentException( + String.format(Locale.ROOT, "Field '%s' must be positive: %d", fieldName, value)); + } + } + + /** + * Checks that the given field is not empty. Only call when the field has a value: + * {@link #required} can be called first, or the check can be conditionally performed. + */ + protected static void nonEmpty(String fieldName, String value) { + if (Preconditions.checkNotNull(value).length() == 0) { + throw new ValidationArgumentException( + String.format(Locale.ROOT, "Field '%s' must be non-empty", fieldName)); + } + } + + /** + * Checks that the given field is not empty. Only call when the field has a value: + * {@link #required} can be called first, or the check can be conditionally performed. + */ + protected static void nonEmpty(String fieldName, Bytes value) { + if (Preconditions.checkNotNull(value).size() == 0) { + throw new ValidationArgumentException( + String.format(Locale.ROOT, "Field '%s' must be non-empty", fieldName)); + } + } + + /** Checks that the given condition holds. */ + protected void check(boolean condition, String message) { + if (!condition) { + throw new ValidationArgumentException(String.format(Locale.ROOT, "%s: %s", message, this)); + } + } + + /** Throws exception indicating a one-of violation due to multiple defined choices. */ + protected static void oneOfViolation(String field1, String field2) { + throw new ValidationArgumentException(String.format(Locale.ROOT, + "Multiple one-of fields defined, including: %s, %s", field1, field2)); + } + + /** Throws exception indicating that no one-of choices are defined. */ + protected static void oneOfViolation() { + throw new ValidationArgumentException("No one-of fields defined"); + } + + // + // Equals helpers. + // + + /** + * Returns {@code true} if the provided objects are both null or are non-null and equal. Returns + * {@code false} otherwise. + */ + protected static boolean equals(Object x, Object y) { + if (x == null) { + return y == null; + } + if (y == null) { + return false; + } + return x.equals(y); + } + + // + // Hash code helpers for primitive types (taken from com.google.common.primitives package). + // + + /** Returns hash code for the provided {@code long} value. */ + protected static int hash(long value) { + // See Longs#hashCode + return (int) (value ^ (value >>> 32)); + } + + /** Returns hash code for the provided {@code int} value. */ + protected static int hash(int value) { + return value; + } + + /** Returns hash code for the provided {@code boolean} value. */ + protected static int hash(boolean value) { + // See Booleans#hashCode + return value ? 1231 : 1237; + } + + /** Returns hash code for the provided {@code float} value. */ + protected static int hash(float value) { + return Float.valueOf(value).hashCode(); + } + + /** Returns hash code for the provided {@code double} value. */ + protected static int hash(double value) { + return Double.valueOf(value).hashCode(); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Smearer.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Smearer.java new file mode 100644 index 0000000..f95f365 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Smearer.java @@ -0,0 +1,58 @@ +/* + * 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.util; + +import java.util.Random; + +/** + * An abstraction to "smear" values by a given percent. Useful for randomizing delays a little bit + * so that (say) processes do not get synchronized on time inadvertently, e.g., a heartbeat task + * that sends a message every few minutes is smeared so that all clients do not end up sending a + * message at the same time. In particular, given a {@code delay}, returns a value that is randomly + * distributed between [delay - smearPercent * delay, delay + smearPercent * delay] + * + */ +public class Smearer { + + private final Random random; + + /** The percentage (0, 1.0] for smearing the delay. */ + private double smearFraction; + + /** + * Creates a smearer with the given random number generator. If {@code smearPercent} is 0, uses an + * internal default for smearing. + * <p> + * REQUIRES: 0 < smearPercent <= 100 + */ + public Smearer(Random random, final int smearPercent) { + Preconditions.checkState((smearPercent >= 0) && (smearPercent <= 100)); + this.random = random; + this.smearFraction = smearPercent / 100.0; + } + + /** + * Given a {@code delay}, returns a value that is randomly distributed between + * [delay - smearPercent * delay, delay + smearPercent * delay] + */ + public int getSmearedDelay(int delay) { + // Get a random number between -1 and 1 and then multiply that by the smear + // fraction. + double smearFactor = (2 * random.nextDouble() - 1.0) * smearFraction; + return (int) Math.ceil(delay + delay * smearFactor); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/TextBuilder.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/TextBuilder.java new file mode 100644 index 0000000..6d55d52 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/TextBuilder.java @@ -0,0 +1,202 @@ +/* + * 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.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * A {@link TextBuilder} is an abstraction that allows classes to efficiently append their string + * representations and then use them later for human consumption, e.g., for debugging or logging. It + * is currently a wrapper around {@link StringBuilder} and {@link Formatter} to give us format and + * append capabilities together. All append methods return this TextBuilder so that the method calls + * can be chained. + * + */ +public class TextBuilder { + + private final StringBuilder builder; + private final UtilFormatter formatter; + + /** + * Given an {@code object} that is an instance of {@code clazz}, outputs names and values of all + * member fields declared on {@code clazz}. This method should be used carefully: + * <ol> + * <li>This method is expensive. For frequently logged types, an ad hoc + * {@link InternalBase#toCompactString} implementation is preferred.</li> + * <li>May overflow the stack if there is a cycle in an object graph.</li> + * <li>Custom formatters have been implemented for many protos. They will not be used by this + * method.</li> + * </ol> + */ + public static void outputFieldsToBuilder(TextBuilder builder, Object object, Class<?> clazz) { + Preconditions.checkArgument(clazz.isAssignableFrom(object.getClass())); + + // Get all the fields and print them using toCompactString if possible; + // otherwise, via toString + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + try { + // Ignore static final fields, as they're uninteresting. + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) { + continue; + } + + field.setAccessible(true); + builder.append(field.getName() + " = "); + Object fieldValue = field.get(object); + if (fieldValue instanceof InternalBase) { + ((InternalBase) fieldValue).toCompactString(builder); + } else { + builder.append(fieldValue); + } + builder.append(", "); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + /** + * Returns an empty TextBuilder to which various objects' string + * representations can be added later. + */ + public TextBuilder() { + builder = new StringBuilder(); + formatter = new UtilFormatter(builder); + } + + /** + * Appends the string representation of {@code c} to this builder. + * + * @param c the character being appended + */ + public TextBuilder append(char c) { + builder.append(c); + return this; + } + + /** + * Appends the string representation of {@code i} to this builder. + * + * @param i the integer being appended + */ + public TextBuilder append(int i) { + builder.append(i); + return this; + } + + /** + * Appends the toString representation of {@code object} to this builder. + */ + public TextBuilder append(Object object) { + builder.append(object); + return this; + } + + /** + * Appends the {@code InternalBase#toCompactString} representation of {@code object} to this + * builder. + */ + public TextBuilder append(InternalBase object) { + if (object == null) { + return append("null"); + } + object.toCompactString(this); + return this; + } + + /** + * Appends the comma-separated {@code InternalBase#toCompactString} representations of + * {@code objects} to this builder. + */ + public TextBuilder append(Iterable<? extends InternalBase> objects) { + if (objects == null) { + return this; + } + boolean first = true; + for (InternalBase object : objects) { + if (first) { + first = false; + } else { + builder.append(", "); + } + append(object); + } + return this; + } + + /** Appends the {@link Bytes#toString} representation of {@code bytes} to this builder. */ + public TextBuilder append(byte[] bytes) { + if (bytes == null) { + return append("null"); + } + Bytes.toCompactString(this, bytes); + return this; + } + + /** + * Appends the string representation of {@code l} to this builder. + * + * @param l the long being appended + */ + public TextBuilder append(long l) { + builder.append(l); + return this; + } + + /** + * Appends the string representation of {@code b} to this builder. + * + * @param b the boolean being appended + */ + public TextBuilder append(boolean b) { + builder.append(b); + return this; + } + + /** + * Appends {@code s} to this builder. + * + * @param s the string being appended + */ + public TextBuilder append(String s) { + builder.append(s); + return this; + } + + /** + * Writes a formatted string to this using the specified format string and + * arguments. + * + * @param format the format as used in {@link java.util.Formatter} + * @param args the arguments that are converted to their string form using + * {@code format} + */ + public TextBuilder appendFormat(String format, Object... args) { + formatter.format(format, args); + return this; + } + + @Override + public String toString() { + return builder.toString(); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/TypedUtil.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/TypedUtil.java new file mode 100644 index 0000000..bb70a80 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/TypedUtil.java @@ -0,0 +1,75 @@ +/* + * 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.util; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + + +/** + * Utilities for using various data structures such as {@link Map}s and {@link + * Set}s in a type-safe manner. + * + */ +public class TypedUtil { + + private TypedUtil() { // prevent instantiation + } + + /** A statically type-safe version of {@link Map#containsKey}. */ + public static <Key> boolean containsKey(Map<Key, ?> map, Key key) { + return map.containsKey(key); + } + + /** A statically type-safe version of {@link Map#get}. */ + public static <Key, Value> Value mapGet(Map<Key, Value> map, Key key) { + return map.get(key); + } + + /** A statically type-safe version of {@link Map#remove}. */ + public static <Key, Value> Value remove(Map<Key, Value> map, Key key) { + return map.remove(key); + } + + /** A statically type-safe version of {@link Set#contains}. */ + public static <ElementType> boolean contains(Set<ElementType> set, ElementType element) { + return set.contains(element); + } + + /** A statically type-safe version of {@link Set#contains}. */ + public static <ElementType> boolean contains( + Collection<ElementType> collection, ElementType element) { + return collection.contains(element); + } + + /** A statically type-safe version of {@link Set#remove}. */ + public static <ElementType> boolean remove(Set<ElementType> set, ElementType element) { + return set.remove(element); + } + + /** + * Typed equals operator (useful to ensure at compile time that the expected types are being + * compared). Returns {@code true} if both arguments are {@code null}, if they reference the same + * object, or if {@code o1.equals(o2)}. + */ + public static <T> boolean equals(T o1, T o2) { + // Not using java.util.Objects, which is not supported by earlier version of Android, and not + // using com.google.common.base.Objects since we're avoiding a Guava dependency for client code. + return (o1 == o2) || ((o1 != null) && o1.equals(o2)); + } +} diff --git a/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/UtilFormatter.java b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/UtilFormatter.java new file mode 100644 index 0000000..3b5a903 --- /dev/null +++ b/third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/UtilFormatter.java @@ -0,0 +1,37 @@ +/* + * 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.util; + +import java.util.Formatter; + +/** + * A formatter with a level of indirection so that GWT magic + * can be used. + * + */ +public class UtilFormatter { + + private final Formatter formatter; + + UtilFormatter(StringBuilder builder) { + formatter = new Formatter(builder); + } + + public void format(String format, Object[] args) { + formatter.format(format, args); + } +} diff --git a/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/AndroidManifest.xml b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/AndroidManifest.xml new file mode 100644 index 0000000..9a2a4db --- /dev/null +++ b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/AndroidManifest.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" ?> +<!-- Copyright 2011 Google Inc. All Rights Reserved. --> +<!-- Manifest for AndroidListener sample application. Must be merged with + j/c/g/ipc/invalidation/external/client/contrib:android_listener_manifest. --> +<manifest android:versionCode="1" android:versionName="0.1" package="com.google.ipc.invalidation.examples.android2" xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- *** WARNING *** DO NOT EDIT! THIS IS GENERATED MANIFEST BY MERGE_MANIFEST TOOL. + Merger manifest: + java/com/google/ipc/invalidation/examples/android2/AndroidManifest.xml + Mergee manifests: + blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml + --> + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="14"/> + <!-- Declare and use permission allowing this application to receive GCM + messages. --> + <permission android:name="com.google.ipc.invalidation.examples.android2.permission.C2D_MESSAGE" android:protectionLevel="signature"/> + <uses-permission android:name="com.google.ipc.invalidation.examples.android2.permission.C2D_MESSAGE"/> + <application> + <!-- Configure the listener class for the application --> + <meta-data android:name="ipc.invalidation.ticl.listener_service_class" android:value="com.google.ipc.invalidation.examples.android2.ExampleListener"/> + <!-- To enable background invalidations uncomment the following element: + --> + <!--<meta-data + android:name= + "ipc.invalidation.ticl.background_invalidation_listener_service_class" + android:value= + "com.google.ipc.invalidation.examples.android2.ExampleListener"/>--> + <!-- Example activity --> + <activity android:name="com.google.ipc.invalidation.examples.android2.MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <!-- Ticl listener. --> + <service android:exported="false" android:name="com.google.ipc.invalidation.examples.android2.ExampleListener"> + <intent-filter> + <action android:name="com.google.ipc.invalidation.AUTH_TOKEN_REQUEST"/> + </intent-filter> + </service> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Receiver for scheduler alarms. --> + <receiver android:exported="false" android:name="com.google.ipc.invalidation.external.client.contrib.AndroidListener$AlarmReceiver"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Receiver for scheduler alarms. --> + <receiver android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.AndroidInternalScheduler$AlarmReceiver"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- GCM Broadcast Receiver --> + <receiver android:exported="true" android:name="com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener$GCMReceiver" android:permission="com.google.android.c2dm.permission.SEND"> + <intent-filter> + <action android:name="com.google.android.c2dm.intent.RECEIVE"/> + <action android:name="com.google.android.c2dm.intent.REGISTRATION"/> + <category android:name="com.google.ipc.invalidation.ticl.android2"/> + </intent-filter> + </receiver> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Merged from file: java/com/google/ipc/invalidation/external/client/android2/AndroidManifest.xml --> + <receiver android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageReceiverService$Receiver"> + <intent-filter> + <action android:name="com.google.ipc.invalidation.gcmmplex.EVENT"/> + </intent-filter> + </receiver> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Ticl service. --> + <service android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.TiclService"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Ticl sender. --> + <service android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageSenderService"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- GCM multiplexer --> + <service android:exported="false" android:name="com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener"> + <meta-data android:name="sender_ids" android:value="ipc.invalidation@gmail.com"/> + </service> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Invalidation service multiplexed GCM receiver --> + <service android:enabled="true" android:exported="false" android:name="com.google.ipc.invalidation.ticl.android2.channel.AndroidMessageReceiverService"/> + </application> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- App receives GCM messages. --> + <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- GCM connects to Google Services. --> + <uses-permission android:name="android.permission.INTERNET"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- GCM requires a Google account. --> + <uses-permission android:name="android.permission.GET_ACCOUNTS"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Merged from file: java/com/google/ipc/invalidation/external/client/android2/AndroidManifest.xml --> + <uses-permission android:name="android.permission.USE_CREDENTIALS"/> + <!-- Merged from file: blaze-out/gcc-4.X.Y-crosstool-v17-hybrid-grtev3-k8-opt/bin/java/com/google/ipc/invalidation/external/client/contrib/android_listener_manifest/AndroidManifest.xml --> + <!-- Keeps the processor from sleeping when a message is received. --> + <uses-permission android:name="android.permission.WAKE_LOCK"/> +</manifest> diff --git a/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListener.java b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListener.java new file mode 100644 index 0000000..3510c13 --- /dev/null +++ b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListener.java @@ -0,0 +1,477 @@ +/* + * 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.examples.android2; + +import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto.ObjectIdProto; +import com.google.ipc.invalidation.external.client.InvalidationClientConfig; +import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState; +import com.google.ipc.invalidation.external.client.contrib.AndroidListener; +import com.google.ipc.invalidation.external.client.types.ErrorInfo; +import com.google.ipc.invalidation.external.client.types.Invalidation; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; +import com.google.protobuf.nano.MessageNano; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Bundle; +import android.util.Base64; +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + + +/** + * Implements the service that handles invalidation client events for this application. It maintains + * state for all objects tracked by the listener (see {@link ExampleListenerState}). By default, the + * listener registers an interest in a small number of objects when started, but it responds to + * registration intents from the main activity (see {@link #createRegisterIntent} and + * {@link #createUnregisterIntent}) so that registrations can be dynamically managed. + * <p> + * Many errors cases in this example implementation are handled by logging errors, which is not the + * appropriate response in a real application where retries or user notification may be needed. + * + */ +public final class ExampleListener extends AndroidListener { + + /** The account type value for Google accounts */ + private static final String GOOGLE_ACCOUNT_TYPE = "com.google"; + + /** + * This is the authentication token type that's used for invalidation client communication to the + * server. For real applications, it would generally match the authorization type used by the + * application. + */ + private static final String AUTH_TYPE = "android"; + + /** Name used for shared preferences. */ + private static final String PREFERENCES_NAME = "example_listener"; + + /** Key used for {@link AndroidListener} state in shared preferences. */ + private static final String ANDROID_LISTENER_STATE_KEY = "android_listener_state"; + + /** Key used for {@link ExampleListener} state in shared preferences. */ + private static final String EXAMPLE_LISTENER_STATE_KEY = "example_listener_state"; + + /** The tag used for logging in the listener. */ + private static final String TAG = "TEA2:EL"; + + /** Ticl client configuration. */ + private static final int CLIENT_TYPE = 4; // Demo client ID. + private static final byte[] CLIENT_NAME = "TEA2:eetrofoot".getBytes(); + + // Intent constants. + private static final String START_INTENT_ACTION = TAG + ":START"; + private static final String STOP_INTENT_ACTION = TAG + ":STOP"; + private static final String REGISTER_INTENT_ACTION = TAG + ":REGISTER"; + private static final String UNREGISTER_INTENT_ACTION = TAG + ":UNREGISTER"; + private static final String OBJECT_ID_EXTRA = "oid"; + + /** Persistent state for the example listener. */ + private ExampleListenerState exampleListenerState; + + public ExampleListener() { + super(); + } + + @Override + public void onCreate() { + super.onCreate(); + + // Deserialize persistent state. + String data = getSharedPreferences().getString(EXAMPLE_LISTENER_STATE_KEY, null); + exampleListenerState = ExampleListenerState.deserialize(data); + } + + @Override + public void onHandleIntent(Intent intent) { + if (intent == null) { + return; + } + + boolean handled = tryHandleRegistrationIntent(intent); + handled = handled || tryHandleStartIntent(intent); + handled = handled || tryHandleStopIntent(intent); + if (!handled) { + super.onHandleIntent(intent); + } + } + + @Override + public void informError(ErrorInfo errorInfo) { + Log.e(TAG, "informError: " + errorInfo); + + /*********************************************************************************************** + * YOUR CODE HERE + * + * Handling of permanent failures is application-specific. + **********************************************************************************************/ + } + + @Override + public void ready(byte[] clientId) { + Log.i(TAG, "ready()"); + exampleListenerState.setClientId(clientId); + writeExampleListenerState(); + } + + @Override + public void reissueRegistrations(byte[] clientId) { + Log.i(TAG, "reissueRegistrations()"); + register(clientId, exampleListenerState.getInterestingObjects()); + } + + @Override + public void invalidate(Invalidation invalidation, byte[] ackHandle) { + Log.i(TAG, "invalidate: " + invalidation); + + exampleListenerState.informInvalidation(invalidation.getObjectId(), invalidation.getVersion(), + invalidation.getPayload(), /* isBackground */ false); + writeExampleListenerState(); + + // Do real work here based upon the invalidation + + acknowledge(ackHandle); + } + + @Override + public void invalidateUnknownVersion(ObjectId objectId, byte[] ackHandle) { + Log.i(TAG, "invalidateUnknownVersion: " + objectId); + + exampleListenerState.informUnknownVersionInvalidation(objectId); + writeExampleListenerState(); + + // In a real app, the application backend would need to be consulted for object state. + + acknowledge(ackHandle); + } + + @Override + public void invalidateAll(byte[] ackHandle) { + Log.i(TAG, "invalidateAll"); + + // Do real work here based upon the invalidation. + exampleListenerState.informInvalidateAll(); + writeExampleListenerState(); + + acknowledge(ackHandle); + } + + + @Override + public byte[] readState() { + Log.i(TAG, "readState"); + SharedPreferences sharedPreferences = getSharedPreferences(); + String data = sharedPreferences.getString(ANDROID_LISTENER_STATE_KEY, null); + return (data != null) ? Base64.decode(data, Base64.DEFAULT) : null; + } + + @Override + public void writeState(byte[] data) { + Log.i(TAG, "writeState"); + Editor editor = getSharedPreferences().edit(); + editor.putString(ANDROID_LISTENER_STATE_KEY, Base64.encodeToString(data, Base64.DEFAULT)); + if (!editor.commit()) { + Log.e(TAG, "failed to write state"); // In a real app, this case would need to handled. + } + } + + @Override + public void requestAuthToken(PendingIntent pendingIntent, + String invalidAuthToken) { + Log.i(TAG, "requestAuthToken"); + + // In response to requestAuthToken, we need to get an auth token and inform the invalidation + // client of the result through a call to setAuthToken. In this example, we block until a + // result is available. It is also possible to invoke setAuthToken in a callback or when + // handling an intent. + AccountManager accountManager = AccountManager.get(getApplicationContext()); + + // Invalidate the old token if necessary. + if (invalidAuthToken != null) { + accountManager.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, invalidAuthToken); + } + + // Choose an (arbitrary in this example) account for which to retrieve an authentication token. + Account account = getAccount(accountManager); + + try { + // There are three possible outcomes of the call to getAuthToken: + // + // 1. Authentication failure (null result). + // 2. The user needs to sign in or give permission for the account. In such cases, the result + // includes an intent that can be started to retrieve credentials from the user. + // 3. The response includes the auth token, in which case we can inform the invalidation + // client. + // + // In the first case, we simply log and return. The response to such errors is application- + // specific. For instance, the application may prompt the user to choose another account. + // + // In the second case, we start an intent to ask for user credentials so that they are + // available to the application if there is a future request. An application should listen for + // the LOGIN_ACCOUNTS_CHANGED_ACTION broadcast intent to trigger a response to the + // invalidation client after the user has responded. Otherwise, it may take several minutes + // for the invalidation client to start. + // + // In the third case, success!, we pass the authorization token and type to the invalidation + // client using the setAuthToken method. + AccountManagerFuture<Bundle> future = accountManager.getAuthToken(account, AUTH_TYPE, + new Bundle(), false, null, null); + Bundle result = future.getResult(); + if (result == null) { + // If the result is null, it means that authentication was not possible. + Log.w(TAG, "Auth token - getAuthToken returned null"); + return; + } + if (result.containsKey(AccountManager.KEY_INTENT)) { + Log.i(TAG, "Starting intent to get auth credentials"); + + // Need to start intent to get credentials. + Intent intent = result.getParcelable(AccountManager.KEY_INTENT); + int flags = intent.getFlags(); + flags |= Intent.FLAG_ACTIVITY_NEW_TASK; + intent.setFlags(flags); + getApplicationContext().startActivity(intent); + return; + } + + Log.i(TAG, "Passing auth token to invalidation client"); + String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); + setAuthToken(getApplicationContext(), pendingIntent, authToken, AUTH_TYPE); + } catch (OperationCanceledException e) { + Log.w(TAG, "Auth token - operation cancelled", e); + } catch (AuthenticatorException e) { + Log.w(TAG, "Auth token - authenticator exception", e); + } catch (IOException e) { + Log.w(TAG, "Auth token - IO exception", e); + } + } + + /** Returns any Google account enabled on the device. */ + private static Account getAccount(AccountManager accountManager) { + if (accountManager == null) { + throw new NullPointerException(); + } + for (Account acct : accountManager.getAccounts()) { + if (GOOGLE_ACCOUNT_TYPE.equals(acct.type)) { + return acct; + } + } + throw new RuntimeException("No google account enabled."); + } + + @Override + public void informRegistrationFailure(byte[] clientId, ObjectId objectId, boolean isTransient, + String errorMessage) { + Log.e(TAG, "Registration failure!"); + if (isTransient) { + // Retry immediately on transient failures. The base AndroidListener will handle exponential + // backoff if there are repeated failures. + List<ObjectId> objectIds = new ArrayList<ObjectId>(); + objectIds.add(objectId); + if (exampleListenerState.isInterestedInObject(objectId)) { + Log.i(TAG, "Retrying registration of " + objectId); + register(clientId, objectIds); + } else { + Log.i(TAG, "Retrying unregistration of " + objectId); + unregister(clientId, objectIds); + } + } + } + + @Override + public void informRegistrationStatus(byte[] clientId, ObjectId objectId, + RegistrationState regState) { + Log.i(TAG, "informRegistrationStatus"); + + List<ObjectId> objectIds = new ArrayList<ObjectId>(); + objectIds.add(objectId); + if (regState == RegistrationState.REGISTERED) { + if (!exampleListenerState.isInterestedInObject(objectId)) { + Log.i(TAG, "Unregistering for object we're no longer interested in"); + unregister(clientId, objectIds); + writeExampleListenerState(); + } + } else { + if (exampleListenerState.isInterestedInObject(objectId)) { + Log.i(TAG, "Registering for an object"); + register(clientId, objectIds); + writeExampleListenerState(); + } + } + } + + @Override + protected void backgroundInvalidateForInternalUse(Iterable<Invalidation> invalidations) { + for (Invalidation invalidation : invalidations) { + Log.i(TAG, "background invalidate: " + invalidation); + exampleListenerState.informInvalidation(invalidation.getObjectId(), invalidation.getVersion(), + invalidation.getPayload(), /* isBackground */ true); + writeExampleListenerState(); + } + } + + /** Creates an intent that registers an interest in object invalidations for {@code objectId}. */ + public static Intent createRegisterIntent(Context context, ObjectId objectId) { + return createRegistrationIntent(context, objectId, /* isRegister */ true); + } + + /** Creates an intent that unregisters for invalidations for {@code objectId}. */ + public static Intent createUnregisterIntent(Context context, ObjectId objectId) { + return createRegistrationIntent(context, objectId, /* isRegister */ false); + } + + private static Intent createRegistrationIntent(Context context, ObjectId objectId, + boolean isRegister) { + Intent intent = new Intent(); + intent.setAction(isRegister ? REGISTER_INTENT_ACTION : UNREGISTER_INTENT_ACTION); + intent.putExtra(OBJECT_ID_EXTRA, serializeObjectId(objectId)); + intent.setClass(context, ExampleListener.class); + return intent; + } + + /** Creates an intent that starts the invalidation client. */ + public static Intent createStartIntent(Context context) { + Intent intent = new Intent(); + intent.setAction(START_INTENT_ACTION); + intent.setClass(context, ExampleListener.class); + return intent; + } + + /** Creates an intent that stops the invalidation client. */ + public static Intent createStopIntent(Context context) { + Intent intent = new Intent(); + intent.setAction(STOP_INTENT_ACTION); + intent.setClass(context, ExampleListener.class); + return intent; + } + + private boolean tryHandleRegistrationIntent(Intent intent) { + final boolean isRegister; + if (REGISTER_INTENT_ACTION.equals(intent.getAction())) { + isRegister = true; + } else if (UNREGISTER_INTENT_ACTION.equals(intent.getAction())) { + isRegister = false; + } else { + // Not a registration intent. + return false; + } + + // Try to parse object id extra. + ObjectId objectId = parseObjectIdExtra(intent); + if (objectId == null) { + Log.e(TAG, "Registration intent without valid object id extra"); + return false; + } + + // Update example listener state. + if (isRegister) { + exampleListenerState.addObjectOfInterest(objectId); + } else { + exampleListenerState.removeObjectOfInterest(objectId); + } + writeExampleListenerState(); + + // If the client is ready, perform registration now. + byte[] clientId = exampleListenerState.getClientId(); + if (clientId == null) { + Log.i(TAG, "Deferring registration until client is ready"); + } else { + // Perform registration immediately if we have been assigned a client id. + List<ObjectId> objectIds = new ArrayList<ObjectId>(1); + objectIds.add(objectId); + if (isRegister) { + register(clientId, objectIds); + } else { + unregister(clientId, objectIds); + } + } + return true; + } + + private boolean tryHandleStartIntent(Intent intent) { + if (START_INTENT_ACTION.equals(intent.getAction())) { + // Clear the client id since a new one will be provided after the client has started. + exampleListenerState.setClientId(null); + writeExampleListenerState(); + + // Setting this to true allows us to see invalidations that may suppress older invalidations. + // When this flag is 'false', AndroidListener#invalidateUnknownVersion is called instead of + // AndroidListener#invalidate when suppression has potentially occurred. + final boolean allowSuppression = true; + InvalidationClientConfig config = new InvalidationClientConfig(CLIENT_TYPE, CLIENT_NAME, + "ExampleListener", allowSuppression); + startService(AndroidListener.createStartIntent(this, config)); + return true; + } + return false; + } + + private boolean tryHandleStopIntent(Intent intent) { + if (STOP_INTENT_ACTION.equals(intent.getAction())) { + // Clear the client id since the client is no longer available. + exampleListenerState.setClientId(null); + writeExampleListenerState(); + startService(AndroidListener.createStopIntent(this)); + return true; + } + return false; + } + + private void writeExampleListenerState() { + Editor editor = getSharedPreferences().edit(); + editor.putString(EXAMPLE_LISTENER_STATE_KEY, exampleListenerState.serialize()); + if (!editor.commit()) { + // In a real app, this case would need to handled. + Log.e(TAG, "failed to write example listener state"); + } + MainActivity.State.setInfo(exampleListenerState.toString()); + } + + private static byte[] serializeObjectId(ObjectId objectId) { + return MessageNano.toByteArray(ExampleListenerState.serializeObjectId(objectId)); + } + + private static ObjectId parseObjectIdExtra(Intent intent) { + byte[] bytes = intent.getByteArrayExtra(OBJECT_ID_EXTRA); + if (bytes == null) { + return null; + } + try { + ObjectIdProto proto = MessageNano.mergeFrom(new ObjectIdProto(), bytes); + return ExampleListenerState.deserializeObjectId(proto); + } catch (InvalidProtocolBufferNanoException exception) { + Log.e(TAG, String.format(Locale.ROOT, "Error parsing object id. error='%s'", + exception.getMessage())); + return null; + } + } + + private SharedPreferences getSharedPreferences() { + return getApplicationContext().getSharedPreferences(PREFERENCES_NAME, MODE_PRIVATE); + } +} diff --git a/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListenerState.java b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListenerState.java new file mode 100644 index 0000000..12ce9be --- /dev/null +++ b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListenerState.java @@ -0,0 +1,333 @@ +/* + * 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.examples.android2; + +import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto; +import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto.ObjectIdProto; +import com.google.ipc.invalidation.examples.android2.ExampleListenerProto.ExampleListenerStateProto.ObjectStateProto; +import com.google.ipc.invalidation.external.client.types.ObjectId; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; +import com.google.protobuf.nano.MessageNano; + +import android.util.Base64; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + + +/** + * Wrapper around persistent state for {@link ExampleListener}. + * + */ +public class ExampleListenerState { + + /** Wrapper around persistent state for an object tracked by the {@link ExampleListener}. */ + private static class ObjectState { + /** Object id for the object being tracked. */ + final ObjectId objectId; + + /** Indicates whether the example listener wants to be registered for this object. */ + boolean isRegistered; + + /** + * Payload of the invalidation with the highest version received so far. {@code null} before + * any invalidations have been received or after an unknown-version invalidation is received. + */ + byte[] payload; + + /** + * Highest version invalidation received so far. {@code null} before any invalidations have + * been received or after an unknown-version invalidation is received. + */ + Long highestVersion; + + /** Wall time in milliseconds at which most recent invalidation was received. */ + Long invalidationTimeMillis; + + /** Indicates whether the last invalidation received was a background invalidation. */ + boolean isBackground; + + ObjectState(ObjectStateProto objectStateProto) { + objectId = deserializeObjectId(objectStateProto.objectId); + isRegistered = objectStateProto.isRegistered; + payload = objectStateProto.payload; + highestVersion = objectStateProto.highestVersion; + invalidationTimeMillis = objectStateProto.invalidationTimeMillis; + isBackground = objectStateProto.isBackground; + } + + ObjectState(ObjectId objectId, boolean isRegistered) { + this.objectId = objectId; + this.isRegistered = isRegistered; + } + + ObjectStateProto serialize() { + ObjectStateProto proto = new ObjectStateProto(); + proto.objectId = serializeObjectId(objectId); + proto.isRegistered = isRegistered; + proto.isBackground = isBackground; + proto.payload = payload; + proto.highestVersion = highestVersion; + proto.invalidationTimeMillis = invalidationTimeMillis; + return proto; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + toString(builder); + return builder.toString(); + } + + void toString(StringBuilder builder) { + builder.append(isRegistered ? "REG " : "UNREG ").append(objectId); + if (payload != null) { + builder.append(", |payload|=").append(payload.length); + } + if (highestVersion != null) { + builder.append(", highestVersion=").append(highestVersion.longValue()); + } + if (isBackground) { + builder.append(", isBackground"); + } + if (invalidationTimeMillis != null) { + builder.append(", invalidationTime=").append(new Date(invalidationTimeMillis.longValue())); + } + } + } + + /** The tag used for logging in the listener state class. */ + private static final String TAG = "TEA2:ELS"; + + /** Number of objects we're interested in tracking by default. */ + + static final int NUM_INTERESTING_OBJECTS = 4; + + /** Object source for objects the client is initially tracking. */ + private static final int DEMO_SOURCE = 4; + + /** Prefix for object names the client is initially tracking. */ + private static final String OBJECT_ID_PREFIX = "Obj"; + + /** State for all tracked objects. */ + private final Map<ObjectId, ObjectState> trackedObjects; + + /** Client id reported by {@code AndroidListener#ready} call. */ + private byte[] clientId; + + private ExampleListenerState(Map<ObjectId, ObjectState> trackedObjects, + byte[] clientId) { + if (trackedObjects == null) { + throw new NullPointerException(); + } + this.trackedObjects = trackedObjects; + this.clientId = clientId; + } + + public static ExampleListenerState deserialize(String data) { + HashMap<ObjectId, ObjectState> trackedObjects = new HashMap<ObjectId, ObjectState>(); + byte[] clientId; + ExampleListenerStateProto stateProto = tryParseStateProto(data); + if (stateProto == null) { + // By default, we're interested in objects with ids Obj1, Obj2, ... + for (int i = 1; i <= NUM_INTERESTING_OBJECTS; ++i) { + ObjectId objectId = ObjectId.newInstance(DEMO_SOURCE, (OBJECT_ID_PREFIX + i).getBytes()); + trackedObjects.put(objectId, new ObjectState(objectId, true)); + } + clientId = null; + } else { + // Load interesting objects from state proto. + for (ObjectStateProto objectStateProto : stateProto.objectState) { + ObjectState objectState = new ObjectState(objectStateProto); + trackedObjects.put(objectState.objectId, objectState); + } + clientId = stateProto.clientId; + } + return new ExampleListenerState(trackedObjects, clientId); + } + + /** Returns proto serialized in data or null if it cannot be decoded. */ + private static ExampleListenerStateProto tryParseStateProto(String data) { + if (data == null) { + return null; + } + final byte[] bytes; + try { + bytes = Base64.decode(data, Base64.DEFAULT); + } catch (IllegalArgumentException exception) { + Log.e(TAG, String.format(Locale.ROOT, "Illegal base 64 encoding. data='%s', error='%s'", data, + exception.getMessage())); + return null; + } + try { + ExampleListenerStateProto proto = + MessageNano.mergeFrom(new ExampleListenerStateProto(), bytes); + return proto; + } catch (InvalidProtocolBufferNanoException exception) { + Log.e(TAG, String.format(Locale.ROOT, "Error parsing state bytes. data='%s', error='%s'", + data, exception.getMessage())); + return null; + } + } + + /** Serializes example listener state to string. */ + public String serialize() { + ExampleListenerStateProto proto = new ExampleListenerStateProto(); + proto.objectState = new ObjectStateProto[trackedObjects.size()]; + int index = 0; + for (ObjectState objectState : trackedObjects.values()) { + ObjectStateProto objectStateProto = objectState.serialize(); + proto.objectState[index++] = objectStateProto; + } + proto.clientId = clientId; + return Base64.encodeToString(MessageNano.toByteArray(proto), Base64.DEFAULT); + } + + Iterable<ObjectId> getInterestingObjects() { + List<ObjectId> interestingObjects = new ArrayList<ObjectId>(trackedObjects.size()); + for (ObjectState objectState : trackedObjects.values()) { + if (objectState.isRegistered) { + interestingObjects.add(objectState.objectId); + } + } + return interestingObjects; + } + + byte[] getClientId() { + return clientId; + } + + /** Sets the client id passed to the example listener via the {@code ready()} call. */ + void setClientId(byte[] value) { + clientId = value; + } + + /** + * Returns {@code true} if the state indicates a registration should exist for the given object. + */ + boolean isInterestedInObject(ObjectId objectId) { + ObjectState objectState = trackedObjects.get(objectId); + return (objectState != null) && objectState.isRegistered; + } + + /** Updates state for the given object to indicate it should be registered. */ + boolean addObjectOfInterest(ObjectId objectId) { + ObjectState objectState = trackedObjects.get(objectId); + if (objectState == null) { + objectState = new ObjectState(objectId, true); + trackedObjects.put(objectId, objectState); + return true; + } + + if (objectState.isRegistered) { + return false; + } + objectState.isRegistered = true; + return true; + } + + /** Updates state for the given object to indicate it should not be registered. */ + boolean removeObjectOfInterest(ObjectId objectId) { + ObjectState objectState = trackedObjects.get(objectId); + if (objectState == null) { + return false; + } + + if (objectState.isRegistered) { + objectState.isRegistered = false; + return true; + } + return false; + } + + /** Updates state for an object after an unknown-version invalidation is received. */ + void informUnknownVersionInvalidation(ObjectId objectId) { + ObjectState objectState = getObjectStateForInvalidation(objectId); + objectState.invalidationTimeMillis = System.currentTimeMillis(); + objectState.highestVersion = null; + objectState.payload = null; + } + + /** Updates state for an object after an invalidation is received. */ + void informInvalidation(ObjectId objectId, long version, byte[] payload, + boolean isBackground) { + ObjectState objectState = getObjectStateForInvalidation(objectId); + if (objectState.highestVersion == null || objectState.highestVersion.longValue() < version) { + objectState.highestVersion = version; + objectState.payload = payload; + objectState.invalidationTimeMillis = System.currentTimeMillis(); + objectState.isBackground = isBackground; + } + } + + /** + * Updates state when an invalidate all request is received (unknown version is marked for all + * objects). + */ + public void informInvalidateAll() { + for (ObjectState objectState : trackedObjects.values()) { + informUnknownVersionInvalidation(objectState.objectId); + } + } + + /** Returns existing object state for an object or updates state. */ + private ObjectState getObjectStateForInvalidation(ObjectId objectId) { + ObjectState objectState = trackedObjects.get(objectId); + if (objectState == null) { + // Invalidation for unregistered object. + objectState = new ObjectState(objectId, false); + trackedObjects.put(objectId, objectState); + } + return objectState; + } + + /** Returns an object given its serialized form. */ + static ObjectId deserializeObjectId(ObjectIdProto objectIdProto) { + return ObjectId.newInstance(objectIdProto.source, objectIdProto.name); + } + + /** Serializes the given object id. */ + static ObjectIdProto serializeObjectId(ObjectId objectId) { + ObjectIdProto proto = new ObjectIdProto(); + proto.source = objectId.getSource(); + proto.name = objectId.getName(); + return proto; + } + + /** Clears all state for the example listener. */ + void clear() { + trackedObjects.clear(); + clientId = null; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (clientId != null) { + builder.append("ready!\n"); + } + for (ObjectState objectState : trackedObjects.values()) { + objectState.toString(builder); + builder.append("\n"); + } + return builder.toString(); + } +} diff --git a/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/MainActivity.java b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/MainActivity.java new file mode 100644 index 0000000..e748ff6 --- /dev/null +++ b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/MainActivity.java @@ -0,0 +1,221 @@ +/* + * 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.examples.android2; + +import com.google.ipc.invalidation.external.client.contrib.MultiplexingGcmListener; +import com.google.ipc.invalidation.external.client.types.ObjectId; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup.LayoutParams; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.nio.charset.Charset; + +/** + * A simple sample application that displays information about object registrations and + * versions. + * + * <p>To submit invalidations, you can run the ExampleServlet: + * + * <p><code> + * blaze run java/com/google/ipc/invalidation/examples:ExampleServlet -- \ + --publisher_spec="" \ + --port=8888 \ + --channel_uri="talkgadget.google.com" + * </code> + * + * <p>and open http://localhost:8888/publisher. + * + * <p>Just publish invalidations with ids similar to 'Obj1', 'Obj2', ... 'Obj3' + * + */ +public final class MainActivity extends Activity { + + /** Tag used in logging. */ + private static final String TAG = "TEA2:MainActivity"; + + /** + * Keep track of current registration and object status. This should probably be implemented + * using intents rather than static state but I don't want to distract from the invalidation + * client essentials in this example. + */ + public static final class State { + private static volatile String info; + private static volatile MainActivity currentActivity; + + public static void setInfo(String info) { + State.info = info; + refreshInfo(); + } + } + + /** Text view showing current {@link ExampleListenerState}. */ + private TextView info; + + /** Text view used to display error messages. */ + private TextView error; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + Log.i(TAG, "[onCreate] Creating main activity"); + super.onCreate(savedInstanceState); + + MultiplexingGcmListener.initializeGcm(this); + + // Setup UI. + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(createClientLifetimeButtons(), LayoutParams.WRAP_CONTENT); + layout.addView(createRegistrationControls(), LayoutParams.WRAP_CONTENT); + error = new TextView(this); + layout.addView(error, LayoutParams.WRAP_CONTENT); + info = new TextView(this); + layout.addView(info, LayoutParams.WRAP_CONTENT); + setContentView(layout); + + // Remember the current activity since the TICL service in this example communicates via + // static state. + State.currentActivity = this; + Log.i(TAG, "[onCreate] Calling refresh data from main activity"); + refreshInfo(); + } + + /** Creates start and stop buttons. */ + private View createClientLifetimeButtons() { + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.HORIZONTAL); + + // Start button. + Button startButton = new Button(this); + startButton.setText("Start"); + startButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + v.getContext().startService(ExampleListener.createStartIntent(v.getContext())); + } + }); + layout.addView(startButton, LayoutParams.WRAP_CONTENT); + + // Stop button. + Button stopButton = new Button(this); + stopButton.setText("Stop"); + stopButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + v.getContext().startService(ExampleListener.createStopIntent(v.getContext())); + } + }); + layout.addView(stopButton, LayoutParams.WRAP_CONTENT); + + return layout; + } + + private View createRegistrationControls() { + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + + // Create the source text field + LinearLayout fieldsLayout = new LinearLayout(this); + fieldsLayout.setOrientation(LinearLayout.HORIZONTAL); + TextView sourceText = new TextView(this); + sourceText.setText("source:"); + fieldsLayout.addView(sourceText, LayoutParams.WRAP_CONTENT); + final EditText sourceField = new EditText(this); + sourceField.setText("DEMO"); + fieldsLayout.addView(sourceField, LayoutParams.WRAP_CONTENT); + + // Create the name text field + TextView nameText = new TextView(this); + nameText.setText("name:"); + fieldsLayout.addView(nameText, LayoutParams.WRAP_CONTENT); + final EditText nameField = new EditText(this); + nameField.setText("Obj1"); + fieldsLayout.addView(nameField, LayoutParams.WRAP_CONTENT); + layout.addView(fieldsLayout, LayoutParams.WRAP_CONTENT); + + // Shared click listener class for registration (register and unregister). + abstract class RegistrationClickListener implements OnClickListener { + @Override + public void onClick(View v) { + String sourceText = sourceField.getText().toString(); + // Build object id from relevant fields. Can't use reflection because this is Android. + final int source; + if ("DEMO".equals(sourceText)) { + source = 4; + } else if ("TEST".equals(sourceText)) { + source = 2; + } else { + error.setText("Unrecognized source: " + sourceText); + return; + } + + String name = nameField.getText().toString(); + ObjectId objectId = + ObjectId.newInstance(source, name.getBytes(Charset.forName("UTF-8"))); + performRegistration(v.getContext(), objectId); + } + + abstract void performRegistration(Context context, ObjectId objectId); + } + + // Create the reg/unreg buttons + LinearLayout buttonsLayout = new LinearLayout(this); + buttonsLayout.setOrientation(LinearLayout.HORIZONTAL); + Button regButton = new Button(this); + regButton.setText("Register"); + regButton.setOnClickListener(new RegistrationClickListener() { + @Override + void performRegistration(Context context, ObjectId objectId) { + context.startService(ExampleListener.createRegisterIntent(context, objectId)); + } + }); + Button unregButton = new Button(this); + unregButton.setText("Unregister"); + unregButton.setOnClickListener(new RegistrationClickListener() { + @Override + void performRegistration(Context context, ObjectId objectId) { + context.startService(ExampleListener.createUnregisterIntent(context, objectId)); + } + }); + buttonsLayout.addView(regButton, LayoutParams.WRAP_CONTENT); + buttonsLayout.addView(unregButton, LayoutParams.WRAP_CONTENT); + layout.addView(buttonsLayout, LayoutParams.WRAP_CONTENT); + return layout; + } + + /** Updates UI with current registration status and object versions. */ + private static void refreshInfo() { + final MainActivity activity = State.currentActivity; + if (null != activity) { + activity.info.post(new Runnable() { + @Override + public void run() { + activity.info.setText(State.info); + } + }); + } + } +} diff --git a/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/example_listener.proto b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/example_listener.proto new file mode 100644 index 0000000..bb6d91e --- /dev/null +++ b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/example_listener.proto @@ -0,0 +1,30 @@ +syntax = "proto2"; +package ipc.invalidation.examples.android2; +option java_package = "com.google.ipc.invalidation.examples.android2"; +option java_outer_classname = "ExampleListenerProto"; + +// Persistent state for the example listener. +message ExampleListenerStateProto { + + message ObjectIdProto { + optional int32 source = 1; + optional bytes name = 2; + } + + // State related to a particular object being tracked by the listener. See + // ExampleListenerState#ObjectState for information on fields. + message ObjectStateProto { + optional ObjectIdProto object_id = 1; + optional bool is_registered = 2; + optional bytes payload = 3; + optional int64 highest_version = 4; + optional int64 invalidation_time_millis = 5; + optional bool is_background = 6; + } + + // List of objects for which state is being tracked. + repeated ObjectStateProto object_state = 1; + + // (Optional) client id passed to the listener in ready() call. + optional bytes client_id = 2; +} diff --git a/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/proguard.cfg b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/proguard.cfg new file mode 100644 index 0000000..9533f06 --- /dev/null +++ b/third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/proguard.cfg @@ -0,0 +1,76 @@ +# +# This file was derived from the Android SDK default configuration in tools/lib/proguard.cfg, +# with changes/additions explicitly commented where made +# +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +# Change: SDK defaults + code/allocation/variable required to disable proguard optimization bug +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +# Change: not needed +#-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native <methods>; +} + +-keepclasseswithmembers class * { + public <init>(android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public <init>(android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} + +# +# All changes below are additions to the Android SDK defaults, generally for the purposes of +# suppressing spurious or inconsequential warnings. +# + +# Suppress duplicate warning for system classes; Blaze is passing android.jar +# to proguard multiple times. +-dontnote android.** +-dontnote java.** +-dontnote javax.** +-dontnote junit.** +-dontnote org.** +-dontnote dalvik.** +-dontnote com.android.internal.** + +# Stop warnings about missing unused classes +-dontwarn com.google.common.annotations.** +-dontwarn com.google.common.base.** +-dontwarn com.google.common.collect.** +-dontnote com.google.common.flags.** +-dontwarn com.google.common.flags.** +-dontwarn com.google.common.util.concurrent.** + +# Ignore missing JDK6 classes +-dontwarn java.** + +# Inverting these produces significant size gains but loses significant debug info +-dontobfuscate +#-flattenpackagehierarchy diff --git a/third_party/cacheinvalidation/src/proto/android_channel.proto b/third_party/cacheinvalidation/src/proto/android_channel.proto new file mode 100644 index 0000000..e4a92f5 --- /dev/null +++ b/third_party/cacheinvalidation/src/proto/android_channel.proto @@ -0,0 +1,96 @@ +/* + * 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. + */ +// +// The Android delivery service's network endpoint id descriptor. +// This proto is internal to the Android channel. + +syntax = "proto2"; + +package com.google.protos.ipc.invalidation; + +option optimize_for = LITE_RUNTIME; + + + +option java_outer_classname = "NanoAndroidChannel"; +option java_package = "com.google.protos.ipc.invalidation"; + + +import "client_protocol.proto"; + +// Defines the valid major versions of the android channel protocol. The +// channel version controls the expected envelope syntax and semantics of +// http and c2dm messages sent between the client and server. +enum MajorVersion { + + // The initial version of the android channel protocol. Inbound and + // outbound channel packets contained a single binary protocol message only. + INITIAL = 0; + + // Adds batching (multiple protocol messages in a single channel message) + BATCH = 1; + + // The default channel version used by Android clients. Lower major numbers + // will represent earlier versions and higher numbers will represent + // experimental versions that are not yet released. + DEFAULT = 0; + + // The minimum and maximum supported channel major versions. Used to validate + // incoming requests, so update as new versions are added or old versions are + // no longer supported. + MIN_SUPPORTED = 0; + MAX_SUPPORTED = 1; +} + +// An id that specifies how to route a message to a Ticl on an Android device +// via C2DM. +message AndroidEndpointId { + // Field 1 was once the ProtocolVersion of this message. + + // The "registration_id" returned when the client registers with c2dm. This + // id is required by c2dm in order to send a message to the device. + optional string c2dm_registration_id = 2; + + // A key identifying a specific client on a device. + optional string client_key = 3; + + // The C2DM sender ID to use to deliver messages to the endpoint. + optional string sender_id = 4 [deprecated = true]; + + // Defines the expected channel version generated by the network endpoint or + // expected in messages sent from the server. + optional Version channel_version = 5; + + // The package name of the Android application that will receive the messages. + // Replaces sender_id. Must be set (unless sender_id is set; in which case it + // must not be set). + optional string package_name = 6; +} + +// A message addressed to a particular Ticl on an Android device. +message AddressedAndroidMessage { + // Client on the device to which the message is destined. + optional string client_key = 1; + + // Message contents (serialized ServerToClientMessage). + optional bytes message = 2; +} + +// A batch of messages addressed to potentially-different Ticls on the same +// Android device. +message AddressedAndroidMessageBatch { + repeated AddressedAndroidMessage addressed_message = 1; +} diff --git a/third_party/cacheinvalidation/src/proto/android_listener.proto b/third_party/cacheinvalidation/src/proto/android_listener.proto new file mode 100644 index 0000000..dd16a83 --- /dev/null +++ b/third_party/cacheinvalidation/src/proto/android_listener.proto @@ -0,0 +1,103 @@ +/* + * 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. + */ +// +// Specification of protocols used by the AndroidListener abstraction. +// +// Note: unless otherwise specified in a comment, all fields in all messages +// are required, even though they are listed as optional. + +syntax = "proto2"; + +package com.google.protos.ipc.invalidation; + +option optimize_for = LITE_RUNTIME; + + +option java_outer_classname = "NanoAndroidListenerProtocol"; +option java_package = "com.google.protos.ipc.invalidation"; + + +import "client.proto"; +import "client_protocol.proto"; + +// Used to persist internal state between instantiations of Android listener +// objects. +message AndroidListenerState { + // When a registration request has failed, we track state for that object that + // allows retries to be delayed using exponential backoff. + message RetryRegistrationState { + // Identifier of the object for which there has been a failure. + optional ObjectIdP object_id = 1; + + // State of exponential backoff delay generator that is used to delay any + // registration retries for the object. + optional ExponentialBackoffState exponential_backoff_state = 2; + } + + // Set of object ids tracking the application's desired registrations. + repeated ObjectIdP registration = 1; + + // Set of states for registrations retries. When there is a transient + // registration failure relative to an object, an entry is added. If + // registration is successful or the user gives up on the request, the entry + // is removed. + repeated RetryRegistrationState retry_registration_state = 2; + + // Identifier of client with which this listener is associated. This client ID + // is randomly generated by the Android listener whenever a new client is + // started and has no relationship to 's application client ID. + optional bytes client_id = 3; + + // Sequence number for alarm manager request codes. Sequence numbers are + // assigned serially for each distinct client_id. This value indicates + // the request code used for the last request. + optional int32 request_code_seq_num = 4; +} + +// Represents a command that registers or unregisters a set of objects. The +// command may be initiated by the application or by the Android listener when +// there is a registration failure. +message RegistrationCommand { + // Indicates whether this is a register command (when true) or unregister + // (when false) request. + optional bool is_register = 1; + + // Identifies the objects to register or unregister. + repeated ObjectIdP object_id = 2; + + // Identifier of client with which this listener is associated. + optional bytes client_id = 3; + + // Indicates whether this is a delayed registration command. When a + // registration command intent is handled by the Android listener, this field + // is used to determine whether the command has been delayed yet or not. If it + // has not already been delayed, the listener may choose to defer the command + // until later. + optional bool is_delayed = 4; +} + +// Represents a command that starts an Android invalidation client. +message StartCommand { + // Type of client to start. + optional int32 client_type = 1; + + // Name of client to start. + optional bytes client_name = 2; + + // Whether suppression is permitted for this client. + optional bool allow_suppression = 3; +} + diff --git a/third_party/cacheinvalidation/src/proto/android_service.proto b/third_party/cacheinvalidation/src/proto/android_service.proto new file mode 100644 index 0000000..e52b7d3 --- /dev/null +++ b/third_party/cacheinvalidation/src/proto/android_service.proto @@ -0,0 +1,182 @@ +/* + * 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. + */ +// +// Specification of protocol buffers that are used with the Android +// service. +// +// Note: unless otherwise specified in a comment, all fields in all messages +// are required, even though they are listed as optional. + +syntax = "proto2"; + +package com.google.protos.ipc.invalidation; + +option optimize_for = LITE_RUNTIME; + + + +option java_outer_classname = "NanoAndroidService"; +option java_package = "com.google.protos.ipc.invalidation"; + + + +import "client_protocol.proto"; +import "java_client.proto"; + +// Call from application to Ticl. +// +// Android service messages are typically validated. Validation rules may be +// declared in ClientProtoWrapperGenerator.java. + +message ClientDowncall { + message StartDowncall {} + message StopDowncall {} + message AckDowncall { + optional bytes ack_handle = 1; + } + message RegistrationDowncall { + repeated ObjectIdP registrations = 1; + repeated ObjectIdP unregistrations = 2; + } + + // Serial number to prevent intent reordering. + // TODO: use. + optional int64 serial = 1; + optional Version version = 2; + + // Exactly one of the following fields must be set. + optional StartDowncall start = 3; + optional StopDowncall stop = 4; + optional AckDowncall ack = 5; + optional RegistrationDowncall registrations = 6; +} + +// Internal (non-public) call from application to Ticl. +message InternalDowncall { + message ServerMessage { + optional bytes data = 1; + } + message NetworkStatus { + optional bool is_online = 1; + } + message CreateClient { + optional int32 client_type = 1; // client type code. + optional bytes client_name = 2; // application client id. + optional ClientConfigP client_config = 3; // Client config. + + // Whether the client should not be started on creation. Must always be + // false for production use. + optional bool skip_start_for_test = 4; + } + optional Version version = 1; + + // Exactly one must be set. + optional ServerMessage server_message = 2; + optional NetworkStatus network_status = 3; + optional bool network_addr_change = 4; + optional CreateClient create_client = 5; +} + +// Upcall from Ticl to application listener. + +message ListenerUpcall { + message ReadyUpcall {} + + message InvalidateUpcall { + // Required. + optional bytes ack_handle = 1; + + // Exactly one must be set. + optional InvalidationP invalidation = 2; + optional ObjectIdP invalidate_unknown = 3; + optional bool invalidate_all = 4; + } + + message RegistrationStatusUpcall { + optional ObjectIdP object_id = 1; + optional bool is_registered = 2; + } + + message RegistrationFailureUpcall { + optional ObjectIdP object_id = 1; + optional bool transient = 2; + optional string message = 3; + } + + message ReissueRegistrationsUpcall { + optional bytes prefix = 1; + optional int32 length = 2; + } + + message ErrorUpcall { + optional int32 error_code = 1; + optional string error_message = 2; + optional bool is_transient = 3; + } + + // Serial number to prevent intent reordering. Not currently used. + // TODO: use + optional int64 serial = 1; + optional Version version = 2; + + // Exactly one must be sent. + optional ReadyUpcall ready = 3; + optional InvalidateUpcall invalidate = 4; + optional RegistrationStatusUpcall registration_status = 5; + optional RegistrationFailureUpcall registration_failure = 6; + optional ReissueRegistrationsUpcall reissue_registrations = 7; + optional ErrorUpcall error = 8; +} + +// Internal proto used by the Android scheduler to represent an event to run. +message AndroidSchedulerEvent { + optional Version version = 1; + + // Name of the recurring task to execute. + optional string event_name = 2; + + // Generation number of the Ticl with which this event is associated. Used to + // prevent old events from accidentally firing on new Ticls. + optional int64 ticl_id = 3; +} + +// Internal proto used by the Android network to represent a message to send +// to the data center from the client. +message AndroidNetworkSendRequest { + optional Version version = 1; // Required + optional bytes message = 2; // Required +} + +// Protocol buffer used to store state for a persisted Ticl. +message AndroidTiclState { + message Metadata { + // All fields are required. + optional int32 client_type = 1; // client type code. + optional bytes client_name = 2; // application client id. + optional int64 ticl_id = 3; // Ticl uniquifier. + optional ClientConfigP client_config = 4; // client config. + } + optional Version version = 1; + optional InvalidationClientState ticl_state = 2; // Marshalled Ticl. + optional Metadata metadata = 3; // Extra state needed to construct a Ticl. +} + +// An AndroidTiclState state plus a digest; this is the protocol buffer actually +// stored persistently by the service. +message AndroidTiclStateWithDigest { + optional AndroidTiclState state = 1; + optional bytes digest = 2; // Digest of "state." +} diff --git a/third_party/cacheinvalidation/src/proto/channel_common.proto b/third_party/cacheinvalidation/src/proto/channel_common.proto new file mode 100644 index 0000000..c68ac74 --- /dev/null +++ b/third_party/cacheinvalidation/src/proto/channel_common.proto @@ -0,0 +1,57 @@ +/* + * 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. + */ +// +// Common utilities used by all channel related protos. +// This is also publicly visible to all channel implementors. + +syntax = "proto2"; + +package com.google.protos.ipc.invalidation; + +option optimize_for = LITE_RUNTIME; + + + +option java_outer_classname = "NanoChannelCommon"; +option java_package = "com.google.protos.ipc.invalidation"; + + + +message ChannelMessageEncoding { + // What kind of encoding is used for network_message + enum MessageEncoding { + // Raw proto encoding + PROTOBUF_BINARY_FORMAT = 1; + + } +} + +message NetworkEndpointId { + enum NetworkAddress { + TEST = 1; // A delivery service for testing + + // Low numbers reserved. + ANDROID = 113; // Android delivery service using c2dm / http. + LCS = 114; // Lightweight connection service () channel. + } + optional NetworkAddress network_address = 1; + optional bytes client_address = 2; + + // Optional. When true, the client is considered offline but the + // client_address is maintained so that the client can potentially be reached. + // When false or undefined, the client is considered online. + optional bool is_offline = 3; +} diff --git a/third_party/cacheinvalidation/src/proto/client.proto b/third_party/cacheinvalidation/src/proto/client.proto new file mode 100644 index 0000000..b81c7f0 --- /dev/null +++ b/third_party/cacheinvalidation/src/proto/client.proto @@ -0,0 +1,80 @@ +/* + * 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. + */ +// +// Specification of protocol buffers that are used only on the client +// side. +// +// Note: unless otherwise specified in a comment, all fields in all messages +// are required, even though they are listed as optional. + +syntax = "proto2"; + +package com.google.protos.ipc.invalidation; + +option optimize_for = LITE_RUNTIME; + + + +option java_outer_classname = "NanoClient"; +option java_package = "com.google.protos.ipc.invalidation"; + + + +import "client_protocol.proto"; + +// An object that is serialized and given to clients for acknowledgement +// purposes. +message AckHandleP { + optional InvalidationP invalidation = 1; +} + +// The state persisted at a client so that it can be used after a reboot. +message PersistentTiclState { + // Last token received from the server (required). + optional bytes client_token = 1; + + // Last time a message was sent to the server (optional). Must be a value + // returned by the clock in the Ticl system resources. + optional int64 last_message_send_time_ms = 2 [default = 0]; +} + +// An envelope containing a Ticl's internal state, along with a digest of the +// serialized representation of this state, to ensure its integrity across +// reads and writes to persistent storage. +message PersistentStateBlob { + // The (important parts of the) Ticl's internal state. + optional PersistentTiclState ticl_state = 1; + + // Implementation-specific message authentication code for the Ticl state. + optional bytes authentication_code = 2; +} + +// State of a Ticl RunState. +message RunStateP { + enum State { + NOT_STARTED = 1; + STARTED = 2; + STOPPED = 3; + } + optional State state = 1; +} + +// Fields in this message correspond directly to fields in +// ExponentialBackoffDelayGenerator. +message ExponentialBackoffState { + optional int32 current_max_delay = 1; + optional bool in_retry_mode = 2; +} diff --git a/third_party/cacheinvalidation/src/proto/client_protocol.proto b/third_party/cacheinvalidation/src/proto/client_protocol.proto new file mode 100644 index 0000000..dabbc05 --- /dev/null +++ b/third_party/cacheinvalidation/src/proto/client_protocol.proto @@ -0,0 +1,619 @@ +/* + * 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. + */ +// +// Specification of protocol buffers and the client-server protocol that +// are used by the clients of the system. +// +// Note: unless otherwise specified in a comment, all fields in all messages +// are required, even though they are listed as optional. + +syntax = "proto2"; + +package com.google.protos.ipc.invalidation; + +option optimize_for = LITE_RUNTIME; + + + +option java_outer_classname = "NanoClientProtocol"; +option java_package = "com.google.protos.ipc.invalidation"; + + + +// Here is a high-level overview of the protocol. The protocol is designed in a +// "message-passing" style, i.e., the client (C) or the server (S) can send any +// message SPONTANEOUSLY at any time and both sides have to be prepared for any +// message from the other side. However, even with this style, there are some +// "flows" which are somewhat like requests and replies. + +// 1. Initialization: When a client starts up, it needs a token to alow it to +// perform any other operation with the server. +// C -> S: InitializeMessage +// S -> C: TokenControlMessage +// +// 2. Registration: When a client has to register or unregister for a set of +// objects, the following flow occurs: +// C -> S: RegistrationMessage +// S -> C: RegistrationStatusMessage +// +// 3. Invalidation: The server sends an invalidation and the client sends back +// an ack. +// S -> C InvalidationMessage +// C -> S InvalidationMessage +// +// 4. Registration sync: Once in a while the server may detect that the client +// and server's registration state is out of sync (the server can detect this +// since it gets the client's registration summary in the client's message +// header). In that case, it asks the client some registration information +// and the client sends it to the server. +// S -> C: RegistrationSyncRequestMessage +// C -> S: RegistrationSyncMessage +// +// 5. Information messages: The server can occasionally for client-side +// information such as statistics, etc. The client responds with the +// requested information +// S -> C: InfoRequestMessage +// C -> S: InfoMessage +// +// Client protocol messages are typically validated. Validation rules may be +// declared in the following locations when making changes to this file: +// +// 1. TiclMessageValidator2.java: validation logic that is run on the +// server. +// +// 2. ClientProtoWrapperGenerator.java: validation logic that is run +// on the client. +// ------------------------------------------------------------------------ + +// A basic message type used for versioning public proto messages and/or +// types. The two fields are supposed to be used as follows: +// +// * The major version number is changed whenever an incompatible protocol +// change or type has been made. When a message/object with a particular +// major version is received, the receiver needs to either know how to handle +// this version or it needs to drop the message +// +// * The minor version can be changed (say) to document some internal change +// for debugging purposes. When a message is received by a receiver, it MUST +// ignore the minor version number for making any protocol/type +// decisions. I.e., the minor version number is for debugging purposes only. +// +// Versioning is used in various places - for entities that are part of a +// protocol (e.g., message requests), for various client implementations, and +// for other data types that change independently of the protocol (e.g., +// session tokens). For each versioned entity, we define a specific message +// type to encapsulate the version of that entity (e.g., ProtocolVersion, +// ClientVersion, etc.). +message Version { + optional int32 major_version = 1; + optional int32 minor_version = 2; +} + +// Message included in all client <-> server messages to indicate the version +// of the protocol in use by the sender. +message ProtocolVersion { + optional Version version = 1; +} + +// Defines a specific version of the client library (for information purposes +// only) May not be used to make decisions at the server (use ProtocolVersion +// instead). +message ClientVersion { + + // A client-specific version number. + optional Version version = 1; + + // All fields below are for informational/debugging/monitoring purposes only. + // No critical code decision is supposed to be made using them. + + // Optional: information about the client operating system/platform, e.g., + // Windows, ChromeOS. + optional string platform = 2; + + // Optional: language used for the library. + optional string language = 3; + + // Optional: extra information about the client (e.g., application name). + optional string application_info = 4; +} + +// Message indicating the result of an operation. +message StatusP { + + // Whether operation is successful or not + enum Code { + SUCCESS = 1; + TRANSIENT_FAILURE = 2; + PERMANENT_FAILURE = 3; + } + + optional Code code = 1; + + // Textual description of the status or additional context about any + // error. (Optional - Can be set for success also.) + optional string description = 2; +} + +// Identifies an object that a client can register for. +message ObjectIdP { + + // The source of the data. + optional int32 source = 1; + + // The id of the object relative to the source. Must be <= 64 bytes. + optional bytes name = 2; +} + +// A message containing the part of the client's id that the application +// controls. This id is used for squelching invalidations on the server side. +// For example, if a client C1 modifies object x and informs the backend about +// C1's application client id as part of the invalidation. The backend can then +// avoid sending the invalidation unnecessarily to that client. +// +// If the application wishes to use this squelching feature, it must assign a +// globally unique client_name for a given client_type so that the particular +// instantation of the application can be identified. +message ApplicationClientIdP { + // The type of the client. + optional int32 client_type = 1; + + // A client name or unique id assigned by the application. Application should + // choose a unique name for different client instances if it wants to squelch + // invalidations by name (as discussed above). + optional bytes client_name = 2; +} + +// Invalidation for a given object/version. +message InvalidationP { + // The id of the object being invalidated. + optional ObjectIdP object_id = 1; + + // Whether the invalidation is for a known version of the object as assigned + // by an application backend (is_known_version == true) or an unknown system + // version synthesized by the invalidation service. (Note that if + // is_known_version is false then is_trickle_restart be true or missing + // because an unknown version implies that invalidation versions prior to the + // current backend version may have been dropped.) + optional bool is_known_version = 2; + + // Version being invalidated (see comment on is_known_version). If the + // is_known_version is false, the version corresponds to an internal "system + // version" for *that* object. An object's system version has no meaning to + // the application other than the fact that these system versions are also + // monotonically increasing and the client must ack such an invalidation with + // this system version (and an ack for a later system version acknowledges an + // invalidation for all earlier system version for *that* object. + optional int64 version = 3; + + // Whether the object's Trickle is restarting at this version. + // sets this value to true to inform Trickle API clients that it may + // have dropped invalidations prior to "version", or, if is_known_version is + // false, prior to the current backend version. This field is logically + // required and is always set by current code. The default is true because + // old Android invalidation clients strip this field when acking + // invalidations due to ProtoLite limitations; true is the correct default + // because invalidation clients logically ack all current versions and + // because old persisted invalidations are all restarted. + optional bool is_trickle_restart = 6 [default = true]; + + // Optional payload associated with this invalidation. + optional bytes payload = 4; + + // DEPRECATED: bridge arrival time is now maintained by + // InvalidationMetadataP in the SourcedInvalidation, InvalidationContents and + // ClientInvalidation containers. + optional int64 bridge_arrival_time_ms_deprecated = 5 [deprecated=true]; +} + +// Specifies the intention to change a registration on a specific object. To +// update registrations, a client sends a message containing repeated +// RegistrationP messages. +message RegistrationP { + enum OpType { + REGISTER = 1; + UNREGISTER = 2; + } + + // The object for which to (un)register. + optional ObjectIdP object_id = 1; + + // Whether to register or unregister. + optional OpType op_type = 2; +} + +// Summary of the registration state associated with a particular client, sent +// in the header of client<->server messages. This summary has two different +// (but related) meanings depending on where it is used: +// +// 1) In a client->server message, it describes the DESIRED client state. +// 2) In a server->client message, it describes the ACTUAL state at the server +// for that client. +message RegistrationSummary { + // Number of registrations desired (client) or held (server). + optional int32 num_registrations = 1; + + // Top-level digest over the registrations. + // + // The digest for an object id is computed as following (the digest chosen for + // this method is SHA-1): + // + // digest = new Digest(); + // digest.update(Little endian encoding of object source type) + // digest.update(object name) + // digest.getDigestSummary() + // + // For a set of objects, digest is computing by sorting lexicographically + // based on their digests and then performing the update process given above + // (i.e., calling digest.update on each object's digest and then calling + // getDigestSummary at the end). + optional bytes registration_digest = 2; +} + +// Header included on every client -> server message. +message ClientHeader { + + // Protocol version of this message. + optional ProtocolVersion protocol_version = 1; + + // Token identifying the client. Tokens are issued by the server in response + // to client requests (see InitializeMessage, below). In order to perform any + // operation other than initialization, the client must supply a token. When + // performing initialization, this field must be left unset. + optional bytes client_token = 2; + + // Optional summary of the client's desired registration state. The client is + // encouraged to provide this summary in every message once a "steady" state + // of registrations/unregistrations has been reached. For example, it may not + // want to send this summary during initialization (but after the initial set + // has been registered, it should try to send it). + optional RegistrationSummary registration_summary = 3; + + // Timestamp from the client's clock, expressed as ms since 00:00:00 UTC, 1 + // January 1970 (i.e., the UNIX epoch) - for debugging/monitoring purposes. + optional int64 client_time_ms = 4; + + // Highest server timestamp observed by the client (the server includes its + // time on every message to the client). Note: this time is NOT necessarily + // expressed as relative to the UNIX epoch - for debugging/monitoring + // purposes. + optional int64 max_known_server_time_ms = 5; + + // Message id to identify the message -for debugging/monitoring purposes. + optional string message_id = 6; + + // Client typecode (as in the InitializeMessage, below). This field may or + // may not be set. + optional int32 client_type = 7; +} + +// A message from the client to the server. +message ClientToServerMessage { + // Header. + optional ClientHeader header = 1; + + // Any or all of the follow messages may be present. + + // Optional initialization message, used to obtain a new token. Note that, if + // present, this message is always processed before the messages below, and + // those messages will be interpreted relative to the new token assigned here. + optional InitializeMessage initialize_message = 2; + + // Optional request to perform registrations. + optional RegistrationMessage registration_message = 3; + + // Optional data for registration sync. + optional RegistrationSyncMessage registration_sync_message = 4; + + // Optional invalidation acks. + optional InvalidationMessage invalidation_ack_message = 5; + + // Optional information about the client. + optional InfoMessage info_message = 6; +} + +// Used to obtain a new token when the client does not have one. +message InitializeMessage { + + // Defines how clients serialize object ids when computing digests for + // registrations. + enum DigestSerializationType { + + // The digest for an object id is computed by serializing the object id into + // bytes. + BYTE_BASED = 1; + + // The digest for an object id is computed by serializing the object id into + // an array of numbers. TODO: Determine and specify this + // more precisely. + NUMBER_BASED = 2; + } + + // Type of the client. This value is assigned by the backend notification + // system (out-of-band) and the client must use the correct value. + optional int32 client_type = 1; + + // Nonce. This value will be echoed as the existing token in the header of + // the server message that supplies the new token (the new token itself will + // be provided in a TokenControlMessage; see below). + optional bytes nonce = 2; + + // Id of the client as assigned by the application. + optional ApplicationClientIdP application_client_id = 3; + + // Type of registration digest used by this client. + optional DigestSerializationType digest_serialization_type = 4; +} + +// Registration operations to perform. +message RegistrationMessage { + repeated RegistrationP registration = 1; +} + +// Message from the client to the server. +message RegistrationSyncMessage { + + // Objects for which the client is registered. + repeated RegistrationSubtree subtree = 1; +} + +// Message sent from the client to the server about registered objects +// (typically) in response to a registration sync request. +// +// The name of the message implies a "tree" for future expansion where the +// intention is to not necessarily send the complete set of objects but to +// partition the object space into multiple ranges and then exchange Merkle-tree +// like data structures to determine which ranges are out-of-sync. +message RegistrationSubtree { + // Registered objects + repeated ObjectIdP registered_object = 1; +} + +// A message from the client to the server with info such as performance +// counters, client os info, etc. +message InfoMessage { + optional ClientVersion client_version = 1; + + // Config parameters used by the client. + // Deprecated and removed - the client_config parameter is what is used now. + repeated PropertyRecord config_parameter = 2; + + // Performance counters from the client. + repeated PropertyRecord performance_counter = 3; + + // If 'true', indicates that the client does not know the server's + // registration summary, so the server should respond with it even if the + // client's summary matches the server's. + optional bool server_registration_summary_requested = 4; + + // Configuration parameters for this client. + optional ClientConfigP client_config = 5; +} + +// Information about a single config/performance counter value in the +// InfoMessage. +message PropertyRecord { + + // Name of the performance counter/config parameter. + optional string name = 1; + + // Value of the performance counter/config parameter. + optional int32 value = 2; +} + +message ServerHeader { + // Protocol version of this message. + optional ProtocolVersion protocol_version = 1; + + // Current token that the server expects the client to have. Clients must + // ignore messages where this token field does not match their current token. + // During initialization, the client's "token" is the nonce that it generates + // and sends in the InitializeMessage. + optional bytes client_token = 2; + + // Summary of registration state held by the server for the client. + optional RegistrationSummary registration_summary = 3; + + // Timestamp from the server's clock. No guarantee on when this time is + // relative to. + optional int64 server_time_ms = 4; + + // Message id to identify the message (for debug purposes only). + optional string message_id = 5; +} + +// If ServerToClientMessage is modified, you need to change the type +// TestServerToClientMessageWithExtraFields in the same way to match. +message ServerToClientMessage { + optional ServerHeader header = 1; + + // Message to assign a new client token or invalidate an existing one. Note + // that, if present, this message is always processed before the messages + // below, and those messages will be interpreted relative to the new token + // assigned here. + optional TokenControlMessage token_control_message = 2; + + // Invalidations. + optional InvalidationMessage invalidation_message = 3; + + // Registration operation replies. + optional RegistrationStatusMessage registration_status_message = 4; + + // Request for client registration state. + optional RegistrationSyncRequestMessage registration_sync_request_message = 5; + + // Request to change config from the server. + optional ConfigChangeMessage config_change_message = 6; + + // Request for client information. + optional InfoRequestMessage info_request_message = 7; + + // Asynchronous error information that the server sends to the client. + optional ErrorMessage error_message = 8; +} + +// Message used to supply a new client token or invalidate an existing one. +message TokenControlMessage { + // If status is failure, new_token cannot be set. + optional bytes new_token = 1; // If missing, means destroy_token +} + +// Status of a particular registration (could be sent spontaneously by the +// server or in response to a registration request). +message RegistrationStatus { + optional RegistrationP registration = 1; + optional StatusP status = 2; +} + +// Registration status of several messages from the server to the client. +message RegistrationStatusMessage { + repeated RegistrationStatus registration_status = 1; +} + +// Request from the server to get the registration info from the client for +// sync purposes. +message RegistrationSyncRequestMessage { +} + +// A set of invalidations from the client to the server or vice-versa +message InvalidationMessage { + repeated InvalidationP invalidation = 1; +} + +// A request from the server to the client for information such as +// performance counters, client os, etc +message InfoRequestMessage { + enum InfoType { + GET_PERFORMANCE_COUNTERS = 1; + } + repeated InfoType info_type = 1; +} + +// A rate limit: a count of events and a window duration in which the events +// may occur. +message RateLimitP { + + // The size of the window over which the rate limit applies. + optional int32 window_ms = 1; + + // The number of events allowed within a given window. + optional int32 count = 2; +} + +// Configuration parameters for the protocol handler in the Ticl. +message ProtocolHandlerConfigP { + // Batching delay - certain messages (e.g., registrations, invalidation acks) + // are sent to the server after this delay. + optional int32 batching_delay_ms = 1 [default = 500]; + + // Rate limits for sending messages. Only two levels allowed currently. + repeated RateLimitP rate_limit = 2; +} + +// Configuration parameters for the Ticl. +message ClientConfigP { + + optional Version version = 1; + + // The delay after which a network message sent to the server is considered + // timed out. + optional int32 network_timeout_delay_ms = 2 [default = 60000]; + + // Retry delay for a persistent write if it fails + optional int32 write_retry_delay_ms = 3 [default = 10000]; + + // Delay for sending heartbeats to the server. + optional int32 heartbeat_interval_ms = 4 [default = 1200000]; + + // Delay after which performance counters are sent to the server. + optional int32 perf_counter_delay_ms = 5 [default = 21600000]; // 6 hours. + + // The maximum exponential backoff factor used for network and persistence + /// timeouts. + optional int32 max_exponential_backoff_factor = 6 [default = 500]; + + // Smearing percent for randomizing delays. + optional int32 smear_percent = 7 [default = 20]; + + // Whether the client is transient, that is, does not write its session + // token to durable storage. + // TODO: need to expose to the clients. + optional bool is_transient = 8 [default = false]; + + // Initial delay for a heartbeat after restarting from persistent state. We + // use this so that the application has a chance to respond to the + // reissueRegistrations call. + optional int32 initial_persistent_heartbeat_delay_ms = 9 [default = 2000]; + + // Configuration for the protocol client to control batching etc. + optional ProtocolHandlerConfigP protocol_handler_config = 10; + + // Whether the channel supports delivery while the client is offline. If + // true, then the servers' use of the channel is such that the + // following holds: if any number of messages are sent to the client while + // the client is unreachable, then the channel will eventually deliver at + // least one message to the client such that, on receiving the message, the + // client will send a message to the server. E.g., the channel could deliver + // a single invalidation or a single registration sync request. C2DM is + // an example of a suitable channel. + // + // When this is true, the Ticl will record in persistent storage the last + // time it sent a message to the server. On persistent restart, it will not + // send a message to the server unless the last one was sent more than a + // heartbeat-interval ago. This is designed to support efficient Android + // clients, which will destroy and recreate the Ticl when transitioning + // between foreground and background states. + optional bool channel_supports_offline_delivery = 11 [default = false]; + + // If the client loses network connectivity, it will send a heartbeat after it + // comes online, unless it had already sent a message more recently than this + // threshold. + optional int32 offline_heartbeat_threshold_ms = 12 [default = 60000]; + + // Whether the client allows suppression. If true (the default), then + // both continuous and restarted invalidations result in an invalidate() + // upcall, which is appropriate for invalidation clients. If false, + // then restarted invalidations result in an invalidateUnknownVersion() + // upcall, which provides correct semantics for Trickles clients. + optional bool allow_suppression = 13 [default = true]; +} + +// A message asking the client to change its configuration parameters +message ConfigChangeMessage { + + // On receipt of this value, do not send any new message to the server + // for the specified delay (this message needs to be accepted without + // any token check). A zero value is ignored by the client. So the lowest + // value for this field is 1. This concept exists to allow the server + // to tell the clients that they should not come back to the server + // for some period of time. + optional int64 next_message_delay_ms = 1; +} + +// An error message that contains an enum for different types of failures with a +// textual description of the failure (as the need arises new error codes will +// be added to this message). +message ErrorMessage { + + enum Code { + AUTH_FAILURE = 1; // Authorization or authentication failure. + UNKNOWN_FAILURE = 10000; // Some failure which is not described above. + }; + + optional Code code = 1; + + // Textual description of the error + optional string description = 2; +} diff --git a/third_party/cacheinvalidation/src/proto/java_client.proto b/third_party/cacheinvalidation/src/proto/java_client.proto new file mode 100644 index 0000000..5c1cbd4 --- /dev/null +++ b/third_party/cacheinvalidation/src/proto/java_client.proto @@ -0,0 +1,100 @@ +/* + * 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. + */ +// +// Specification of protocol buffers that are used to marshall the +// in-memory state of an invalidation client. +// +// Note: unless otherwise specified in a comment, all fields in all messages +// are required, even though they are listed as optional. + +syntax = "proto2"; + +package com.google.protos.ipc.invalidation; + +option optimize_for = LITE_RUNTIME; + + + +option java_outer_classname = "NanoJavaClient"; +option java_package = "com.google.protos.ipc.invalidation"; + + + +import 'client.proto'; +import 'client_protocol.proto'; + +// State of the batched messages in the ProtocolHandler. Corresponds to +// ProtocolHandler.Batcher. +message BatcherState { + repeated ObjectIdP registration = 1; + repeated ObjectIdP unregistration = 2; + repeated InvalidationP acknowledgement = 3; + repeated RegistrationSubtree registration_subtree = 4; + optional InitializeMessage initialize_message = 5; + optional InfoMessage info_message = 6; +} + +// State of the protocol handler. Fields correspond directly to fields in +// ProtocolHandler.java. +message ProtocolHandlerState { + optional int32 message_id = 1; + optional int64 last_known_server_time_ms = 2; + optional int64 next_message_send_time_ms = 3; + optional BatcherState batcher_state = 4; +} + +// State of the registration manager. +message RegistrationManagerStateP { + repeated ObjectIdP registrations = 1; + optional RegistrationSummary last_known_server_summary = 2; + repeated RegistrationP pending_operations = 3; +} + +// State of a recurring task. Fields correspond directly to fields in +// RecurringTask.java. +message RecurringTaskState { + optional int32 initial_delay_ms = 1; + optional int32 timeout_delay_ms = 2; + optional bool scheduled = 3; + optional ExponentialBackoffState backoff_state = 4; +} + +// State of the statistics object. Marshalling is done by marshalling the +// SimplePairs returned by Statistics.fillWithNonZeroStatistics into +// PropertyRecords. +message StatisticsState { + repeated PropertyRecord counter = 1; +} + +// State of the invalidation client. Fields correspond directly to fields in +// InvalidationClientImpl.java. +message InvalidationClientState { + optional RunStateP run_state = 1; + optional bytes client_token = 2; + optional bytes nonce = 3; + optional bool should_send_registrations = 4; + optional int64 last_message_send_time_ms = 5; + optional bool is_online = 6; + optional ProtocolHandlerState protocol_handler_state = 7; + optional RegistrationManagerStateP registration_manager_state = 8; + optional RecurringTaskState acquire_token_task_state = 9; + optional RecurringTaskState reg_sync_heartbeat_task_state = 10; + optional RecurringTaskState persistent_write_task_state = 11; + optional RecurringTaskState heartbeat_task_state = 12; + optional RecurringTaskState batching_task_state = 13; + optional PersistentTiclState last_written_state = 14; + optional StatisticsState statistics_state = 15; +} diff --git a/third_party/cacheinvalidation/src/proto/types.proto b/third_party/cacheinvalidation/src/proto/types.proto new file mode 100644 index 0000000..e1bba02 --- /dev/null +++ b/third_party/cacheinvalidation/src/proto/types.proto @@ -0,0 +1,68 @@ +// 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. + +// Enums definitions for main types in the cache invalidation system. + +syntax = "proto2"; + + +package com.google.protos.ipc.invalidation; + +option optimize_for = LITE_RUNTIME; + +// The type of client / application. +message ClientType { + enum Type { + INTERNAL = 1; + TEST = 2; // Uncontrolled client space for use by anyone for testing. + DEMO = 4; // A demo client type that can be used for testing. + + // Numbers below 1000 are reserved for internal use. + CHROME_SYNC = 1004; + CHROME_SYNC_ANDROID = 1018; + CHROME_SYNC_IOS = 1038; + CHROME_SYNC_GCM_DESKTOP = 1055; + CHROME_SYNC_GCM_IOS = 1056; + } + optional Type type = 1; +} + +// The property that hosts the object. +message ObjectSource { + // + // NOTE: This enum MUST be kept in sync with ObjectIdP.Source in + // internal.proto. + // + enum Type { + INTERNAL = 1; + TEST = 2; // Uncontrolled object space for use by anyone for testing. + DEMO = 4; // A demo object source that can be used for testing. + + // Numbers below 1000 are reserved for internal use. + CHROME_SYNC = 1004; + COSMO_CHANGELOG = 1014; + CHROME_COMPONENTS = 1025; + CHROME_PUSH_MESSAGING = 1030; + } + optional Type type = 1; +} + +// A dummy message to enclose various enum constant declarations. +message Constants { + // Constants related to object versions. + enum ObjectVersion { + // Version number used to indicate that an object's version is unknown. + UNKNOWN = 0; + } +} |