/* * 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.common.ClientProtocolAccessor.ApplicationClientIdPAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ClientConfigPAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ClientHeaderAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ClientToServerMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ClientVersionAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ConfigChangeMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ErrorMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.InfoMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.InfoRequestMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.InitializeMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.InvalidationMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.InvalidationPAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ObjectIdPAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ProtocolHandlerConfigPAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.RateLimitPAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.RegistrationMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.RegistrationPAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.RegistrationStatusAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.RegistrationStatusMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.RegistrationSubtreeAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.RegistrationSummaryAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.RegistrationSyncMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ServerHeaderAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.ServerToClientMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.StatusPAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.TokenControlMessageAccessor; import com.google.ipc.invalidation.common.ClientProtocolAccessor.VersionAccessor; import com.google.ipc.invalidation.util.BaseLogger; import com.google.protobuf.MessageLite; import com.google.protos.ipc.invalidation.ClientProtocol.ApplicationClientIdP; import com.google.protos.ipc.invalidation.ClientProtocol.ClientHeader; import com.google.protos.ipc.invalidation.ClientProtocol.ClientToServerMessage; import com.google.protos.ipc.invalidation.ClientProtocol.ConfigChangeMessage; import com.google.protos.ipc.invalidation.ClientProtocol.InitializeMessage; import com.google.protos.ipc.invalidation.ClientProtocol.InvalidationP; import com.google.protos.ipc.invalidation.ClientProtocol.ObjectIdP; import com.google.protos.ipc.invalidation.ClientProtocol.RateLimitP; import com.google.protos.ipc.invalidation.ClientProtocol.RegistrationSummary; import com.google.protos.ipc.invalidation.ClientProtocol.ServerHeader; import com.google.protos.ipc.invalidation.ClientProtocol.ServerToClientMessage; import com.google.protos.ipc.invalidation.ClientProtocol.Version; /** * Validator for v2 protocol messages. * <p> * The basic idea is to declare information about each field in each message, i.e., whether it is * optional or it is required. {@code FieldInfo} is a class that keeps track of information * about each field and {@code MessageInfo} is a class that keeps track of information about each * message. Given a message, we recursively descend a MessageInfo and determine if the fields * are as expected. Then once we are done with a message, we perform a post validation step * which checks constraints across fields. * */ public class TiclMessageValidator2 extends ProtoValidator { public TiclMessageValidator2(BaseLogger logger) { super(logger); } /** Describes how to validate common messages. */ public class CommonMsgInfos { /** Validation for composite (major/minor) versions. */ 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)) { logger.info("Invalid versions: %s", version); return false; } return true; } }; /** Validation for the protocol version. */ final MessageInfo PROTOCOL_VERSION = new MessageInfo( ClientProtocolAccessor.PROTOCOL_VERSION_ACCESSOR, FieldInfo.newRequired(ClientProtocolAccessor.ProtocolVersionAccessor.VERSION, VERSION)); /** Validation for object ids. */ final MessageInfo OID = new MessageInfo(ClientProtocolAccessor.OBJECT_ID_P_ACCESSOR, FieldInfo.newRequired(ObjectIdPAccessor.NAME), FieldInfo.newRequired(ObjectIdPAccessor.SOURCE)) { @Override public boolean postValidate(MessageLite message) { // Must have non-negative source code. ObjectIdP oid = (ObjectIdP) message; if (oid.getSource() < 0) { logger.info("Source was negative: %s", oid); return false; } return true; } }; /** Validation for invalidations. */ final MessageInfo INVALIDATION = new MessageInfo( ClientProtocolAccessor.INVALIDATION_P_ACCESSOR, FieldInfo.newRequired(InvalidationPAccessor.OBJECT_ID, OID), FieldInfo.newRequired(InvalidationPAccessor.IS_KNOWN_VERSION), FieldInfo.newRequired(InvalidationPAccessor.VERSION), FieldInfo.newOptional(InvalidationPAccessor.PAYLOAD), FieldInfo.newOptional(InvalidationPAccessor.IS_TRICKLE_RESTART), FieldInfo.newOptional(InvalidationPAccessor.BRIDGE_ARRIVAL_TIME_MS_DEPRECATED)) { @Override public boolean postValidate(MessageLite message) { // Must have non-negative version. InvalidationP invalidation = (InvalidationP) message; if (invalidation.getVersion() < 0) { logger.info("Version was negative: %s", invalidation); return false; } boolean isUnknownVersion = !invalidation.getIsKnownVersion(); // Note that a missing value for is_trickle_restart is treated like a true value, // becomes it comes from a downlevel client that uses invalidation semantics. boolean isTrickleRestart = !invalidation.hasIsTrickleRestart() || invalidation.getIsTrickleRestart(); if (isUnknownVersion && !isTrickleRestart) { logger.info( "if is_known_version is false, is_trickle_restart must be true or missing: %s", invalidation); return false; } return true; } }; /** Validation for a message containing many invalidations. */ final MessageInfo INVALIDATION_MSG; /** Validation for a single registration description. */ final MessageInfo REGISTRATIONP = new MessageInfo( ClientProtocolAccessor.REGISTRATION_P_ACCESSOR, FieldInfo.newRequired(RegistrationPAccessor.OBJECT_ID, OID), FieldInfo.newRequired(RegistrationPAccessor.OP_TYPE)); /** Validation for a summary of registration state. */ final MessageInfo REGISTRATION_SUMMARY = new MessageInfo( ClientProtocolAccessor.REGISTRATION_SUMMARY_ACCESSOR, FieldInfo.newRequired(RegistrationSummaryAccessor.NUM_REGISTRATIONS), FieldInfo.newRequired(RegistrationSummaryAccessor.REGISTRATION_DIGEST)) { @Override public boolean postValidate(MessageLite message) { RegistrationSummary summary = (RegistrationSummary) message; return (summary.getNumRegistrations() >= 0) && (!summary.getRegistrationDigest().isEmpty()); } }; final MessageInfo RATE_LIMIT = new MessageInfo( ClientProtocolAccessor.RATE_LIMIT_P_ACCESSOR, FieldInfo.newRequired(RateLimitPAccessor.WINDOW_MS), FieldInfo.newRequired(RateLimitPAccessor.COUNT)) { @Override public boolean postValidate(MessageLite message) { RateLimitP rateLimit = (RateLimitP) message; return (rateLimit.getWindowMs() >= 1000) && (rateLimit.getWindowMs() > rateLimit.getCount()); } }; final MessageInfo PROTOCOL_HANDLER_CONFIG = new MessageInfo( ClientProtocolAccessor.PROTOCOL_HANDLER_CONFIG_P_ACCESSOR, FieldInfo.newOptional(ProtocolHandlerConfigPAccessor.BATCHING_DELAY_MS), FieldInfo.newOptional(ProtocolHandlerConfigPAccessor.RATE_LIMIT, RATE_LIMIT) ); // Validation for Client Config. */ final MessageInfo CLIENT_CONFIG = new MessageInfo( ClientProtocolAccessor.CLIENT_CONFIG_P_ACCESSOR, FieldInfo.newRequired(ClientConfigPAccessor.VERSION, VERSION), FieldInfo.newOptional(ClientConfigPAccessor.NETWORK_TIMEOUT_DELAY_MS), FieldInfo.newOptional(ClientConfigPAccessor.WRITE_RETRY_DELAY_MS), FieldInfo.newOptional(ClientConfigPAccessor.HEARTBEAT_INTERVAL_MS), FieldInfo.newOptional(ClientConfigPAccessor.PERF_COUNTER_DELAY_MS), FieldInfo.newOptional(ClientConfigPAccessor.MAX_EXPONENTIAL_BACKOFF_FACTOR), FieldInfo.newOptional(ClientConfigPAccessor.SMEAR_PERCENT), FieldInfo.newOptional(ClientConfigPAccessor.IS_TRANSIENT), FieldInfo.newOptional(ClientConfigPAccessor.INITIAL_PERSISTENT_HEARTBEAT_DELAY_MS), FieldInfo.newOptional(ClientConfigPAccessor.CHANNEL_SUPPORTS_OFFLINE_DELIVERY), FieldInfo.newRequired(ClientConfigPAccessor.PROTOCOL_HANDLER_CONFIG, PROTOCOL_HANDLER_CONFIG), FieldInfo.newOptional(ClientConfigPAccessor.OFFLINE_HEARTBEAT_THRESHOLD_MS), FieldInfo.newOptional(ClientConfigPAccessor.ALLOW_SUPPRESSION) ); private CommonMsgInfos() { // Initialize in constructor since other instance fields are referenced INVALIDATION_MSG = new MessageInfo( ClientProtocolAccessor.INVALIDATION_MESSAGE_ACCESSOR, FieldInfo.newRequired(InvalidationMessageAccessor.INVALIDATION, this.INVALIDATION)); } } /** Describes how to validate client messages. */ private class ClientMsgInfos { /** Validation for client headers. */ final MessageInfo HEADER = new MessageInfo( ClientProtocolAccessor.CLIENT_HEADER_ACCESSOR, FieldInfo.newRequired(ClientHeaderAccessor.PROTOCOL_VERSION, commonMsgInfos.PROTOCOL_VERSION), FieldInfo.newOptional(ClientHeaderAccessor.CLIENT_TOKEN), FieldInfo.newOptional(ClientHeaderAccessor.REGISTRATION_SUMMARY, commonMsgInfos.REGISTRATION_SUMMARY), FieldInfo.newRequired(ClientHeaderAccessor.CLIENT_TIME_MS), FieldInfo.newRequired(ClientHeaderAccessor.MAX_KNOWN_SERVER_TIME_MS), FieldInfo.newOptional(ClientHeaderAccessor.MESSAGE_ID), FieldInfo.newOptional(ClientHeaderAccessor.CLIENT_TYPE)) { @Override public boolean postValidate(MessageLite message) { ClientHeader header = (ClientHeader) message; // If set, token must not be empty. if (header.hasClientToken() && header.getClientToken().isEmpty()) { logger.info("Client token was set but empty: %s", header); return false; } // If set, message id must not be empty. // Do not use String.isEmpty() here for Froyo (JDK5) compatibility. if (header.hasMessageId() && (header.getMessageId().length() == 0)) { logger.info("Message id was set but empty: %s", header); return false; } if (header.getClientTimeMs() < 0) { logger.info("Client time was negative: %s", header); return false; } if (header.getMaxKnownServerTimeMs() < 0) { logger.info("Max known server time was negative: %s", header); return false; } return true; } }; /** Validation for application client ids. */ final MessageInfo APPLICATION_CLIENT_ID = new MessageInfo( // Client type is optional here since the registrar needs to accept messages from // the ticls that do not set the client type. ClientProtocolAccessor.APPLICATION_CLIENT_ID_P_ACCESSOR, FieldInfo.newOptional(ApplicationClientIdPAccessor.CLIENT_TYPE), FieldInfo.newRequired(ApplicationClientIdPAccessor.CLIENT_NAME)) { @Override public boolean postValidate(MessageLite message) { ApplicationClientIdP applicationClientId = (ApplicationClientIdP) message; return !applicationClientId.getClientName().isEmpty(); } }; /** Validation for client initialization messages. */ final MessageInfo INITIALIZE_MESSAGE = new MessageInfo( ClientProtocolAccessor.INITIALIZE_MESSAGE_ACCESSOR, FieldInfo.newRequired(InitializeMessageAccessor.CLIENT_TYPE), FieldInfo.newRequired(InitializeMessageAccessor.NONCE), FieldInfo.newRequired(InitializeMessageAccessor.DIGEST_SERIALIZATION_TYPE), FieldInfo.newRequired(InitializeMessageAccessor.APPLICATION_CLIENT_ID, APPLICATION_CLIENT_ID)) { @Override public boolean postValidate(MessageLite message) { return ((InitializeMessage) message).getClientType() >= 0; } }; /** Validation for registration requests. */ final MessageInfo REGISTRATION = new MessageInfo( ClientProtocolAccessor.REGISTRATION_MESSAGE_ACCESSOR, FieldInfo.newRequired(RegistrationMessageAccessor.REGISTRATION, commonMsgInfos.REGISTRATIONP)); /** Validation for client versions. */ final MessageInfo CLIENT_VERSION = new MessageInfo( ClientProtocolAccessor.CLIENT_VERSION_ACCESSOR, FieldInfo.newRequired(ClientVersionAccessor.VERSION, commonMsgInfos.VERSION), FieldInfo.newRequired(ClientVersionAccessor.PLATFORM), FieldInfo.newRequired(ClientVersionAccessor.LANGUAGE), FieldInfo.newRequired(ClientVersionAccessor.APPLICATION_INFO)); /** Validation for client information messages. */ final MessageInfo INFO = new MessageInfo( ClientProtocolAccessor.INFO_MESSAGE_ACCESSOR, FieldInfo.newRequired(InfoMessageAccessor.CLIENT_VERSION, CLIENT_VERSION), FieldInfo.newOptional(InfoMessageAccessor.CONFIG_PARAMETER), FieldInfo.newOptional(InfoMessageAccessor.PERFORMANCE_COUNTER), FieldInfo.newOptional(InfoMessageAccessor.CLIENT_CONFIG, commonMsgInfos.CLIENT_CONFIG), FieldInfo.newOptional(InfoMessageAccessor.SERVER_REGISTRATION_SUMMARY_REQUESTED)); /** Validation for registration subtrees. */ final MessageInfo SUBTREE = new MessageInfo( ClientProtocolAccessor.REGISTRATION_SUBTREE_ACCESSOR, FieldInfo.newOptional(RegistrationSubtreeAccessor.REGISTERED_OBJECT)); /** Validation for registration sync messages. */ final MessageInfo REGISTRATION_SYNC = new MessageInfo( ClientProtocolAccessor.REGISTRATION_SYNC_MESSAGE_ACCESSOR, FieldInfo.newRequired(RegistrationSyncMessageAccessor.SUBTREE, SUBTREE)); /** Validation for a ClientToServerMessage. */ final MessageInfo CLIENT_MSG = new MessageInfo( ClientProtocolAccessor.CLIENT_TO_SERVER_MESSAGE_ACCESSOR, FieldInfo.newRequired(ClientToServerMessageAccessor.HEADER, HEADER), FieldInfo.newOptional(ClientToServerMessageAccessor.INFO_MESSAGE, INFO), FieldInfo.newOptional(ClientToServerMessageAccessor.INITIALIZE_MESSAGE, INITIALIZE_MESSAGE), FieldInfo.newOptional(ClientToServerMessageAccessor.INVALIDATION_ACK_MESSAGE, commonMsgInfos.INVALIDATION_MSG), FieldInfo.newOptional(ClientToServerMessageAccessor.REGISTRATION_MESSAGE, REGISTRATION), FieldInfo.newOptional(ClientToServerMessageAccessor.REGISTRATION_SYNC_MESSAGE, REGISTRATION_SYNC)) { @Override public boolean postValidate(MessageLite message) { ClientToServerMessage parsedMessage = (ClientToServerMessage) message; // The message either has an initialize request from the client or it has the client token. return (parsedMessage.hasInitializeMessage() ^ parsedMessage.getHeader().hasClientToken()); } }; } /** Describes how to validate server messages. */ class ServerMsgInfos { /** Validation for server headers. */ final MessageInfo HEADER = new MessageInfo( ClientProtocolAccessor.SERVER_HEADER_ACCESSOR, FieldInfo.newRequired(ServerHeaderAccessor.PROTOCOL_VERSION, commonMsgInfos.PROTOCOL_VERSION), FieldInfo.newRequired(ServerHeaderAccessor.CLIENT_TOKEN), FieldInfo.newOptional(ServerHeaderAccessor.REGISTRATION_SUMMARY, commonMsgInfos.REGISTRATION_SUMMARY), FieldInfo.newRequired(ServerHeaderAccessor.SERVER_TIME_MS), FieldInfo.newOptional(ServerHeaderAccessor.MESSAGE_ID)) { @Override public boolean postValidate(MessageLite message) { ServerHeader header = (ServerHeader) message; if (header.getClientToken().isEmpty()) { logger.info("Client token was empty: %s", header); return false; } if (header.getServerTimeMs() < 0) { logger.info("Server time was negative: %s", header); return false; } // If set, message id must not be empty. // Do not use String.isEmpty() here for Froyo (JDK5) compatibility. if (header.hasMessageId() && (header.getMessageId().length() == 0)) { logger.info("Message id was set but empty: %s", header); return false; } return true; } }; /** Validation for server response codes. */ final MessageInfo STATUSP = new MessageInfo( ClientProtocolAccessor.STATUS_P_ACCESSOR, FieldInfo.newRequired(StatusPAccessor.CODE), FieldInfo.newOptional(StatusPAccessor.DESCRIPTION)); /** Validation for token control messages. */ final MessageInfo TOKEN_CONTROL = new MessageInfo( ClientProtocolAccessor.TOKEN_CONTROL_MESSAGE_ACCESSOR, FieldInfo.newOptional(TokenControlMessageAccessor.NEW_TOKEN)); /** Validation for error messages. */ final MessageInfo ERROR = new MessageInfo( ClientProtocolAccessor.ERROR_MESSAGE_ACCESSOR, FieldInfo.newRequired(ErrorMessageAccessor.CODE), FieldInfo.newRequired(ErrorMessageAccessor.DESCRIPTION)); /** Validation for registration results. */ final MessageInfo REGISTRATION_RESULT = new MessageInfo( ClientProtocolAccessor.REGISTRATION_STATUS_ACCESSOR, FieldInfo.newRequired(RegistrationStatusAccessor.REGISTRATION, commonMsgInfos.REGISTRATIONP), FieldInfo.newRequired(RegistrationStatusAccessor.STATUS, STATUSP)); /** Validation for registration status messages. */ final MessageInfo REGISTRATION_STATUS_MSG = new MessageInfo( ClientProtocolAccessor.REGISTRATION_STATUS_MESSAGE_ACCESSOR, FieldInfo.newRequired(RegistrationStatusMessageAccessor.REGISTRATION_STATUS, REGISTRATION_RESULT)); /** Validation for registration sync requests. */ final MessageInfo REGISTRATION_SYNC_REQUEST = new MessageInfo( ClientProtocolAccessor.REGISTRATION_SYNC_REQUEST_MESSAGE_ACCESSOR); /** Validation for info requests. */ final MessageInfo INFO_REQUEST = new MessageInfo( ClientProtocolAccessor.INFO_REQUEST_MESSAGE_ACCESSOR, FieldInfo.newRequired(InfoRequestMessageAccessor.INFO_TYPE)); /** Validation for config change message. */ final MessageInfo CONFIG_CHANGE = new MessageInfo( ClientProtocolAccessor.CONFIG_CHANGE_MESSAGE_ACCESSOR, FieldInfo.newOptional(ConfigChangeMessageAccessor.NEXT_MESSAGE_DELAY_MS)) { @Override public boolean postValidate(MessageLite message) { ConfigChangeMessage parsedMessage = (ConfigChangeMessage) message; // If the message has a next_message_delay_ms value, it must be positive. return !parsedMessage.hasNextMessageDelayMs() || (parsedMessage.getNextMessageDelayMs() > 0); } }; /** Validation for the top-level server messages. */ final MessageInfo SERVER_MSG = new MessageInfo( ClientProtocolAccessor.SERVER_TO_CLIENT_MESSAGE_ACCESSOR, FieldInfo.newRequired(ServerToClientMessageAccessor.HEADER, HEADER), FieldInfo.newOptional(ServerToClientMessageAccessor.TOKEN_CONTROL_MESSAGE, TOKEN_CONTROL), FieldInfo.newOptional(ServerToClientMessageAccessor.INVALIDATION_MESSAGE, commonMsgInfos.INVALIDATION_MSG), FieldInfo.newOptional(ServerToClientMessageAccessor.REGISTRATION_STATUS_MESSAGE, REGISTRATION_STATUS_MSG), FieldInfo.newOptional(ServerToClientMessageAccessor.REGISTRATION_SYNC_REQUEST_MESSAGE, REGISTRATION_SYNC_REQUEST), FieldInfo.newOptional(ServerToClientMessageAccessor.CONFIG_CHANGE_MESSAGE, CONFIG_CHANGE), FieldInfo.newOptional(ServerToClientMessageAccessor.INFO_REQUEST_MESSAGE, INFO_REQUEST), FieldInfo.newOptional(ServerToClientMessageAccessor.ERROR_MESSAGE, ERROR)); } /** Common validation information */ final CommonMsgInfos commonMsgInfos = new CommonMsgInfos(); /** Client validation information */ private final ClientMsgInfos clientMsgInfos = new ClientMsgInfos(); /** Server validation information */ final ServerMsgInfos serverMsgInfos = new ServerMsgInfos(); /** Returns whether {@code clientMessage} is valid. */ public boolean isValid(ClientToServerMessage clientMessage) { return checkMessage(clientMessage, clientMsgInfos.CLIENT_MSG); } /** Returns whether {@code serverMessage} is valid. */ public boolean isValid(ServerToClientMessage serverMessage) { return checkMessage(serverMessage, serverMsgInfos.SERVER_MSG); } /** Returns whether {@code invalidation} is valid. */ public boolean isValid(InvalidationP invalidation) { return checkMessage(invalidation, commonMsgInfos.INVALIDATION); } /** Returns whether {@code version} is valid. */ public boolean isValid(Version version) { return checkMessage(version, commonMsgInfos.VERSION); } /** Returns the {@code MessageInfo} for a {@link ServerToClientMessage}. */ public MessageInfo getServerToClientMessageInfo() { return serverMsgInfos.SERVER_MSG; } }