/*
* 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.ClientProtocolAccessor;
import com.google.ipc.invalidation.common.ClientProtocolAccessor.VersionAccessor;
import com.google.ipc.invalidation.common.ProtoValidator;
import com.google.ipc.invalidation.external.client.SystemResources.Logger;
import com.google.ipc.invalidation.ticl.android2.AndroidServiceAccessor.AndroidNetworkSendRequestAccessor;
import com.google.ipc.invalidation.ticl.android2.AndroidServiceAccessor.AndroidTiclStateAccessor;
import com.google.ipc.invalidation.ticl.android2.AndroidServiceAccessor.AndroidTiclStateAccessor.MetadataAccessor;
import com.google.ipc.invalidation.ticl.android2.AndroidServiceAccessor.AndroidTiclStateWithDigestAccessor;
import com.google.ipc.invalidation.ticl.android2.AndroidServiceAccessor.ClientDowncallAccessor;
import com.google.ipc.invalidation.ticl.android2.AndroidServiceAccessor.InternalDowncallAccessor;
import com.google.ipc.invalidation.ticl.android2.AndroidServiceAccessor.ListenerUpcallAccessor;
import com.google.protobuf.MessageLite;
import com.google.protos.ipc.invalidation.AndroidService.AndroidNetworkSendRequest;
import com.google.protos.ipc.invalidation.AndroidService.AndroidSchedulerEvent;
import com.google.protos.ipc.invalidation.AndroidService.AndroidTiclStateWithDigest;
import com.google.protos.ipc.invalidation.AndroidService.ClientDowncall;
import com.google.protos.ipc.invalidation.AndroidService.InternalDowncall;
import com.google.protos.ipc.invalidation.AndroidService.ListenerUpcall;
import com.google.protos.ipc.invalidation.ClientProtocol.Version;
/**
* Validator for Android internal protocol intents and messages.
* <p>
* This class works by defining instances of {@code MessageInfo} for each protocol buffer
* that requires validation. A {@code MessageInfo} takes two parameters: an <i>accessor</i>
* that allows it to read the fields of an instance of the message to be validated, and a list
* of {@code FieldInfo} objects that, for each field in the message, specify whether the field
* is required or optional. Additionally, a {@code FieldInfo} may have a reference to a
* {@code MessageInfo} object that specifies how to recursively validate the field.
* <p>
* For example, the validation for the {@code ACK} downcall protocol buffer is specified as follows:
* <code>
* static final MessageInfo ACK = new MessageInfo(
* ClientDowncallAccessor.ACK_DOWNCALL_ACCESSOR,
* FieldInfo.newRequired(ClientDowncallAccessor.AckDowncallAccessor.ACK_HANDLE));
* </code>
* This specifies that the {@code ACK_DOWNCALL_ACCESSOR} is to be used to read the fields of
* protocol buffers to be validated, and that instances of those protocol buffers should have
* exactly one field (ack handle) set.
* <p>
* For a more complicated example, the {{@link DowncallMessageInfos#REGISTRATIONS} validator
* requires one or the other (but not both) of two fields to be set, and those fields are
* recursively validated.
*
*/
public final class AndroidIntentProtocolValidator extends ProtoValidator {
/** Validation for composite (major/minor) versions. */
static final MessageInfo VERSION = new MessageInfo(ClientProtocolAccessor.VERSION_ACCESSOR,
FieldInfo.newRequired(VersionAccessor.MAJOR_VERSION),
FieldInfo.newRequired(VersionAccessor.MINOR_VERSION)) {
@Override
public boolean postValidate(MessageLite message) {
// Versions must be non-negative.
Version version = (Version) message;
if ((version.getMajorVersion() < 0) || (version.getMinorVersion() < 0)) {
return false;
}
return true;
}
};
/** Validation for public client downcalls. */
static class DowncallMessageInfos {
static final MessageInfo ACK = new MessageInfo(
ClientDowncallAccessor.ACK_DOWNCALL_ACCESSOR,
FieldInfo.newRequired(ClientDowncallAccessor.AckDowncallAccessor.ACK_HANDLE));
static final MessageInfo REGISTRATIONS = new MessageInfo(
ClientDowncallAccessor.REGISTRATION_DOWNCALL_ACCESSOR,
FieldInfo.newOptional(ClientDowncallAccessor.RegistrationDowncallAccessor.REGISTRATIONS),
FieldInfo.newOptional(
ClientDowncallAccessor.RegistrationDowncallAccessor.UNREGISTRATIONS)) {
@Override
public boolean postValidate(MessageLite message) {
int numSetFields = 0;
for (FieldInfo fieldInfo : getAllFields()) {
if (ClientDowncallAccessor.REGISTRATION_DOWNCALL_ACCESSOR.hasField(
message, fieldInfo.getFieldDescriptor())) {
++numSetFields;
}
}
return numSetFields == 1; // Registrations or unregistrations, but not both.
}
};
static final MessageInfo DOWNCALL_MSG = new MessageInfo(
AndroidServiceAccessor.CLIENT_DOWNCALL_ACCESSOR,
FieldInfo.newRequired(AndroidServiceAccessor.ClientDowncallAccessor.VERSION, VERSION),
FieldInfo.newOptional(AndroidServiceAccessor.ClientDowncallAccessor.SERIAL),
FieldInfo.newOptional(AndroidServiceAccessor.ClientDowncallAccessor.ACK, ACK),
FieldInfo.newOptional(
AndroidServiceAccessor.ClientDowncallAccessor.REGISTRATIONS, REGISTRATIONS),
FieldInfo.newOptional(AndroidServiceAccessor.ClientDowncallAccessor.START),
FieldInfo.newOptional(AndroidServiceAccessor.ClientDowncallAccessor.STOP)) {
@Override
public boolean postValidate(MessageLite message) {
int numSetFields = 0;
for (FieldInfo fieldInfo : getAllFields()) {
if (AndroidServiceAccessor.CLIENT_DOWNCALL_ACCESSOR.hasField(
message, fieldInfo.getFieldDescriptor())) {
++numSetFields;
}
}
return numSetFields == 2; // Version plus exactly one operation. Serial not currently used.
}
};
}
/** Validation for client internal downcalls. */
static class InternalDowncallInfos {
private static MessageInfo NETWORK_STATUS = new MessageInfo(
InternalDowncallAccessor.NETWORK_STATUS_ACCESSOR,
FieldInfo.newRequired(InternalDowncallAccessor.NetworkStatusAccessor.IS_ONLINE));
private static MessageInfo SERVER_MESSAGE = new MessageInfo(
InternalDowncallAccessor.SERVER_MESSAGE_ACCESSOR,
FieldInfo.newRequired(InternalDowncallAccessor.ServerMessageAccessor.DATA));
// We do not post-validate the config in this message, since the Ticl should be doing it, and
// it's not clear that we should be peering into Ticl protocol buffers anyway.
private static MessageInfo CREATE_CLIENT_MESSAGE = new MessageInfo(
InternalDowncallAccessor.CREATE_CLIENT_ACCESSOR,
FieldInfo.newRequired(InternalDowncallAccessor.CreateClientAccessor.CLIENT_CONFIG),
FieldInfo.newRequired(InternalDowncallAccessor.CreateClientAccessor.CLIENT_NAME),
FieldInfo.newRequired(InternalDowncallAccessor.CreateClientAccessor.CLIENT_TYPE),
FieldInfo.newRequired(InternalDowncallAccessor.CreateClientAccessor.SKIP_START_FOR_TEST));
static final MessageInfo INTERNAL_DOWNCALL_MSG = new MessageInfo(
AndroidServiceAccessor.INTERNAL_DOWNCALL_ACCESSOR,
FieldInfo.newRequired(InternalDowncallAccessor.VERSION, VERSION),
FieldInfo.newOptional(InternalDowncallAccessor.NETWORK_STATUS, NETWORK_STATUS),
FieldInfo.newOptional(InternalDowncallAccessor.SERVER_MESSAGE, SERVER_MESSAGE),
FieldInfo.newOptional(InternalDowncallAccessor.NETWORK_ADDR_CHANGE),
FieldInfo.newOptional(InternalDowncallAccessor.CREATE_CLIENT, CREATE_CLIENT_MESSAGE)) {
@Override
public boolean postValidate(MessageLite message) {
int numSetFields = 0;
for (FieldInfo fieldInfo : getAllFields()) {
if (AndroidServiceAccessor.INTERNAL_DOWNCALL_ACCESSOR.hasField(
message, fieldInfo.getFieldDescriptor())) {
++numSetFields;
}
}
return numSetFields == 2; // Version plus exactly one operation. Serial not currently used.
}
};
}
/** Validation for listener upcalls. */
static class ListenerUpcallInfos {
static final MessageInfo ERROR = new MessageInfo(ListenerUpcallAccessor.ERROR_UPCALL_ACCESSOR,
FieldInfo.newRequired(ListenerUpcallAccessor.ErrorUpcallAccessor.ERROR_CODE),
FieldInfo.newRequired(ListenerUpcallAccessor.ErrorUpcallAccessor.ERROR_MESSAGE),
FieldInfo.newRequired(ListenerUpcallAccessor.ErrorUpcallAccessor.IS_TRANSIENT));
// TODO: validate INVALIDATE_UNKNOWN and INVALIDATION sub-messages.
static final MessageInfo INVALIDATE = new MessageInfo(
ListenerUpcallAccessor.INVALIDATE_UPCALL_ACCESSOR,
FieldInfo.newRequired(ListenerUpcallAccessor.InvalidateUpcallAccessor.ACK_HANDLE),
FieldInfo.newOptional(ListenerUpcallAccessor.InvalidateUpcallAccessor.INVALIDATE_ALL),
FieldInfo.newOptional(ListenerUpcallAccessor.InvalidateUpcallAccessor.INVALIDATE_UNKNOWN),
FieldInfo.newOptional(ListenerUpcallAccessor.InvalidateUpcallAccessor.INVALIDATION)) {
@Override
public boolean postValidate(MessageLite message) {
int numSetFields = 0;
for (FieldInfo fieldInfo : getAllFields()) {
if (ListenerUpcallAccessor.INVALIDATE_UPCALL_ACCESSOR.hasField(
message, fieldInfo.getFieldDescriptor())) {
++numSetFields;
}
}
return numSetFields == 2; // Handle plus exactly one operation. Serial not currently used.
}
};
static final MessageInfo REGISTRATION_FAILURE = new MessageInfo(
ListenerUpcallAccessor.REGISTRATION_FAILURE_UPCALL_ACCESSOR,
FieldInfo.newRequired(ListenerUpcallAccessor.RegistrationFailureUpcallAccessor.MESSAGE),
FieldInfo.newRequired(ListenerUpcallAccessor.RegistrationFailureUpcallAccessor.OBJECT_ID),
FieldInfo.newRequired(ListenerUpcallAccessor.RegistrationFailureUpcallAccessor.TRANSIENT));
static final MessageInfo REGISTRATION_STATUS = new MessageInfo(
ListenerUpcallAccessor.REGISTRATION_STATUS_UPCALL_ACCESSOR,
FieldInfo.newRequired(
ListenerUpcallAccessor.RegistrationStatusUpcallAccessor.IS_REGISTERED),
FieldInfo.newRequired(ListenerUpcallAccessor.RegistrationStatusUpcallAccessor.OBJECT_ID));
static final MessageInfo REISSUE_REGISTRATIONS = new MessageInfo(
ListenerUpcallAccessor.REISSUE_REGISTRATIONS_UPCALL_ACCESSOR,
FieldInfo.newRequired(ListenerUpcallAccessor.ReissueRegistrationsUpcallAccessor.LENGTH),
FieldInfo.newRequired(ListenerUpcallAccessor.ReissueRegistrationsUpcallAccessor.PREFIX));
static final MessageInfo LISTENER_UPCALL_MESSAGE = new MessageInfo(
AndroidServiceAccessor.LISTENER_UPCALL_ACCESSOR,
FieldInfo.newRequired(ListenerUpcallAccessor.VERSION, VERSION),
FieldInfo.newOptional(ListenerUpcallAccessor.SERIAL),
FieldInfo.newOptional(ListenerUpcallAccessor.ERROR, ERROR),
FieldInfo.newOptional(ListenerUpcallAccessor.INVALIDATE, INVALIDATE),
FieldInfo.newOptional(ListenerUpcallAccessor.READY),
FieldInfo.newOptional(ListenerUpcallAccessor.REGISTRATION_FAILURE, REGISTRATION_FAILURE),
FieldInfo.newOptional(ListenerUpcallAccessor.REGISTRATION_STATUS, REGISTRATION_STATUS),
FieldInfo.newOptional(
ListenerUpcallAccessor.REISSUE_REGISTRATIONS, REISSUE_REGISTRATIONS)) {
@Override
public boolean postValidate(MessageLite message) {
int numSetFields = 0;
for (FieldInfo fieldInfo : getAllFields()) {
if (AndroidServiceAccessor.LISTENER_UPCALL_ACCESSOR.hasField(
message, fieldInfo.getFieldDescriptor())) {
++numSetFields;
}
}
return numSetFields == 2; // Version plus exactly one operation. Serial not currently used.
}
};
}
/** Validation for internal protocol buffers. */
static class InternalInfos {
static final MessageInfo ANDROID_SCHEDULER_EVENT = new MessageInfo(
AndroidServiceAccessor.ANDROID_SCHEDULER_EVENT_ACCESSOR,
FieldInfo.newRequired(
AndroidServiceAccessor.AndroidSchedulerEventAccessor.VERSION, VERSION),
FieldInfo.newRequired(AndroidServiceAccessor.AndroidSchedulerEventAccessor.EVENT_NAME),
FieldInfo.newRequired(AndroidServiceAccessor.AndroidSchedulerEventAccessor.TICL_ID));
static final MessageInfo ANDROID_NETWORK_SEND_REQUEST = new MessageInfo(
AndroidServiceAccessor.ANDROID_NETWORK_SEND_REQUEST_ACCESSOR,
FieldInfo.newRequired(AndroidNetworkSendRequestAccessor.VERSION, VERSION),
FieldInfo.newRequired(AndroidNetworkSendRequestAccessor.MESSAGE));
// We do not post-validate the config in this message, since the Ticl should be doing it, and
// it's not clear that we should be peering into Ticl protocol buffers anyway.
static final MessageInfo PERSISTED_STATE_METADATA = new MessageInfo(
AndroidTiclStateAccessor.METADATA_ACCESSOR,
FieldInfo.newRequired(MetadataAccessor.CLIENT_CONFIG),
FieldInfo.newRequired(MetadataAccessor.CLIENT_NAME),
FieldInfo.newRequired(MetadataAccessor.CLIENT_TYPE),
FieldInfo.newRequired(MetadataAccessor.TICL_ID));
static final MessageInfo ANDROID_TICL_STATE = new MessageInfo(
AndroidServiceAccessor.ANDROID_TICL_STATE_ACCESSOR,
FieldInfo.newRequired(AndroidTiclStateAccessor.METADATA, PERSISTED_STATE_METADATA),
FieldInfo.newRequired(AndroidTiclStateAccessor.TICL_STATE),
FieldInfo.newRequired(AndroidTiclStateAccessor.VERSION));
static final MessageInfo ANDROID_TICL_STATE_WITH_DIGEST = new MessageInfo(
AndroidServiceAccessor.ANDROID_TICL_STATE_WITH_DIGEST_ACCESSOR,
FieldInfo.newRequired(AndroidTiclStateWithDigestAccessor.DIGEST),
FieldInfo.newRequired(AndroidTiclStateWithDigestAccessor.STATE, ANDROID_TICL_STATE));
}
/** Returns whether {@code downcall} has a valid set of fields with valid values. */
boolean isDowncallValid(ClientDowncall downcall) {
return checkMessage(downcall, DowncallMessageInfos.DOWNCALL_MSG);
}
/** Returns whether {@code downcall} has a valid set of fields with valid values. */
boolean isInternalDowncallValid(InternalDowncall downcall) {
return checkMessage(downcall, InternalDowncallInfos.INTERNAL_DOWNCALL_MSG);
}
/** Returns whether {@code upcall} has a valid set of fields with valid values. */
boolean isListenerUpcallValid(ListenerUpcall upcall) {
return checkMessage(upcall, ListenerUpcallInfos.LISTENER_UPCALL_MESSAGE);
}
/** Returns whether {@code event} has a valid set of fields with valid values. */
boolean isSchedulerEventValid(AndroidSchedulerEvent event) {
return checkMessage(event, InternalInfos.ANDROID_SCHEDULER_EVENT);
}
/** Returns whether {@code request} has a valid set of fields with valid values. */
public boolean isNetworkSendRequestValid(AndroidNetworkSendRequest request) {
return checkMessage(request, InternalInfos.ANDROID_NETWORK_SEND_REQUEST);
}
/**
* Returns whether {@code state} has a valid set of fields with valid values. Does not
* verify the digest.
*/
boolean isTiclStateValid(AndroidTiclStateWithDigest state) {
return checkMessage(state, InternalInfos.ANDROID_TICL_STATE_WITH_DIGEST);
}
public AndroidIntentProtocolValidator(Logger logger) {
super(logger);
}
}