summaryrefslogtreecommitdiffstats
path: root/third_party
diff options
context:
space:
mode:
authorzea <zea@chromium.org>2015-06-03 10:51:25 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-03 17:52:09 +0000
commit4a996cdc7a36a71ac511c153375fc6170fea80e6 (patch)
treed8009a33833db8d801117502eca0e69ec6262503 /third_party
parent5fc460f2bd3055f9142567a7b6e12d1b9e17de3c (diff)
downloadchromium_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')
-rw-r--r--third_party/cacheinvalidation/DEPS3
-rw-r--r--third_party/cacheinvalidation/README.chromium23
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/AndroidManifest.xml92
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/README8
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/ant.properties1
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/build.xml88
-rwxr-xr-xthird_party/cacheinvalidation/src/example-app-build/generate_protos.sh22
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/libs/gcm.jarbin0 -> 13662 bytes
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/libs/protobuf-java-2.3.0-nano.jarbin0 -> 384273 bytes
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/local.properties10
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/proguard.cfg76
-rw-r--r--third_party/cacheinvalidation/src/example-app-build/project.properties11
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/COPYING202
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/README16
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/android_channel.proto91
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/channel_common.proto49
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/client.proto64
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/client_gateway.proto38
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/client_protocol.proto595
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/client_test_internal.proto31
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/callback.h82
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/digest-function.h46
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/gmock.h20
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/googletest.h21
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/logging.h22
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/mutex.h26
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/random.h36
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/scoped_ptr.h20
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/sha1-digest-function.h22
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/stl-namespace.h22
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/string_util.h22
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/deps/time.h22
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/basic-system-resources.cc82
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/basic-system-resources.h68
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/build_constants.h22
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/checking-invalidation-listener.cc133
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/checking-invalidation-listener.h88
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/client-protocol-namespace-fix.h81
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/constants.cc29
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/constants.h48
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/digest-store.h89
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/exponential-backoff-delay-generator.cc40
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/exponential-backoff-delay-generator.h79
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-core.cc1009
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-core.h490
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-factory.cc76
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-factory_test.cc114
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl.cc79
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl.h122
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-impl_test.cc504
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/invalidation-client-util.h40
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/log-macro.h23
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/object-id-digest-utils.cc38
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/object-id-digest-utils.h49
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/persistence-utils.cc61
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/persistence-utils.h54
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-converter.cc62
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-converter.h65
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-helpers.cc473
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/proto-helpers.h143
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler.cc442
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler.h424
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/protocol-handler_test.cc674
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task.cc81
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task.h128
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/recurring-task_test.cc236
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/registration-manager.cc135
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/registration-manager.h198
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/repeated-field-namespace-fix.h30
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/run-state.h87
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/safe-storage.cc74
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/safe-storage.h72
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/simple-registration-store.cc107
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/simple-registration-store.h87
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/smearer.h62
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/statistics.cc121
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/statistics.h234
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle.cc113
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle.h88
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/throttle_test.cc191
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/ticl-message-validator.cc369
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/impl/ticl-message-validator.h55
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-client-factory.h162
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-client.h116
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/include/invalidation-listener.h193
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/include/system-resources.h266
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/include/types.h369
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/test/deterministic-scheduler.cc84
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/test/deterministic-scheduler.h152
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-logger.cc53
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-logger.h37
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-utils.cc254
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/test/test-utils.h320
-rw-r--r--third_party/cacheinvalidation/src/google/cacheinvalidation/types.proto67
-rw-r--r--third_party/cacheinvalidation/src/java/COPYING202
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/BaseCommonInvalidationConstants.java40
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/BuildConstants.java37
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/DigestFunction.java34
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/common/ObjectIdDigestUtils.java102
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClient.java108
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClientConfig.java43
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationClientFactory.java49
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/InvalidationListener.java177
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/SystemResources.java211
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/SystemResourcesBuilder.java174
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android/service/AndroidLogger.java230
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android2/AndroidClientFactory.java53
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/android2/AndroidManifest.xml58
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListener.java661
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerIntents.java220
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerManifest.xml12
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerProtos.java112
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/AndroidListenerState.java304
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/contrib/MultiplexingGcmListener.java359
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/AckHandle.java68
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ApplicationClientId.java77
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/BytesFormatter.java101
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Callback.java33
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ErrorContext.java38
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ErrorInfo.java93
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Invalidation.java136
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/ObjectId.java85
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/SimplePair.java115
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/external/client/types/Status.java122
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/AckCache.java96
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/BasicSystemResources.java125
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/CheckingInvalidationListener.java178
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/DigestStore.java81
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/InvalidationClientCore.java1546
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/InvalidationClientImpl.java163
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/MemoryStorageImpl.java146
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/PersistenceUtils.java76
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/ProtoWrapperConverter.java113
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/ProtocolHandler.java661
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RecurringTask.java256
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RegistrationManager.java284
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/RunState.java95
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/SafeStorage.java111
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/SimpleRegistrationStore.java148
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/Statistics.java353
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TestableInvalidationClient.java158
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TestableNetworkChannel.java33
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/TiclExponentialBackoffDelayGenerator.java57
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidClock.java36
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInternalScheduler.java208
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationClientImpl.java260
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationClientStub.java108
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationListenerIntentMapper.java153
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidInvalidationListenerStub.java84
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidManifest.xml15
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidStorage.java164
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/AndroidTiclManifest.java163
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/ProtocolIntents.java262
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/ResourcesFactory.java137
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclService.java389
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/TiclStateManager.java243
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/WakeLockManager.java224
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidChannelConstants.java128
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidChannelPreferences.java106
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidMessageReceiverService.java135
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidMessageSenderService.java400
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/android2/channel/AndroidNetworkChannel.java62
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidChannel.java354
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidListenerProtocol.java501
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/AndroidService.java2009
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ChannelCommon.java216
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/Client.java483
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ClientConstants.java55
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/ClientProtocol.java3171
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/CommonProtos.java120
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/ticl/proto/JavaClient.java1076
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/BaseLogger.java63
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Box.java59
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Bytes.java348
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/ExponentialBackoffDelayGenerator.java126
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Formatter.java72
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/InternalBase.java94
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/LazyString.java144
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Marshallable.java27
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/NamedRunnable.java43
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Preconditions.java78
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/ProtoWrapper.java237
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/Smearer.java58
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/TextBuilder.java202
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/TypedUtil.java75
-rw-r--r--third_party/cacheinvalidation/src/java/com/google/ipc/invalidation/util/UtilFormatter.java37
-rw-r--r--third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/AndroidManifest.xml92
-rw-r--r--third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListener.java477
-rw-r--r--third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/ExampleListenerState.java333
-rw-r--r--third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/MainActivity.java221
-rw-r--r--third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/example_listener.proto30
-rw-r--r--third_party/cacheinvalidation/src/javaexample/com/google/ipc/invalidation/examples/android2/proguard.cfg76
-rw-r--r--third_party/cacheinvalidation/src/proto/android_channel.proto96
-rw-r--r--third_party/cacheinvalidation/src/proto/android_listener.proto103
-rw-r--r--third_party/cacheinvalidation/src/proto/android_service.proto182
-rw-r--r--third_party/cacheinvalidation/src/proto/channel_common.proto57
-rw-r--r--third_party/cacheinvalidation/src/proto/client.proto80
-rw-r--r--third_party/cacheinvalidation/src/proto/client_protocol.proto619
-rw-r--r--third_party/cacheinvalidation/src/proto/java_client.proto100
-rw-r--r--third_party/cacheinvalidation/src/proto/types.proto68
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
new file mode 100644
index 0000000..ac109a8
--- /dev/null
+++ b/third_party/cacheinvalidation/src/example-app-build/libs/gcm.jar
Binary files differ
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
new file mode 100644
index 0000000..b51536a
--- /dev/null
+++ b/third_party/cacheinvalidation/src/example-app-build/libs/protobuf-java-2.3.0-nano.jar
Binary files differ
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(&registered_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,
+ &registration_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,
+ &registration_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, &reg_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;
+ }
+}