| // Copyright 2010 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT 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/session-manager.h" |
| |
| #include "google/cacheinvalidation/invalidation-client.h" |
| #include "google/cacheinvalidation/log-macro.h" |
| #include "google/cacheinvalidation/logging.h" |
| #include "google/cacheinvalidation/types.pb.h" |
| |
| namespace invalidation { |
| |
| void SessionManager::UpdateState() { |
| if (uniquifier_.empty()) { |
| CHECK(session_token_.empty()); |
| state_ = State_NO_UNIQUIFIER_OR_SESSION; |
| } else if (session_token_.empty()) { |
| state_ = State_UNIQUIFIER_ONLY; |
| } else { |
| state_ = State_UNIQUIFIER_AND_SESSION; |
| } |
| } |
| |
| bool SessionManager::IsMessageIntendedForClient( |
| const ServerToClientMessage& message) { |
| // Intentional fall-throughs in switch statement. |
| switch (message.message_type()) { |
| case ServerToClientMessage_MessageType_TYPE_ASSIGN_CLIENT_ID: |
| case ServerToClientMessage_MessageType_TYPE_UPDATE_SESSION: |
| case ServerToClientMessage_MessageType_TYPE_INVALIDATE_CLIENT_ID: |
| case ServerToClientMessage_MessageType_TYPE_INVALIDATE_SESSION: |
| // The session manager decides whether or not to handle these messages |
| // based on its own internal state, so we just always say that they're |
| // intended for us and then do those verification checks later, when we're |
| // given the message back. |
| return true; |
| |
| case ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL: |
| return HasSession() && (session_token_ == message.session_token()); |
| |
| default: |
| CHECK(false); |
| return false; |
| } |
| } |
| |
| void SessionManager::AddSessionAction(ClientToServerMessage* message) { |
| // This method is called by takeOutboundMessage() to determine the type of all |
| // outbound messages. |
| message->mutable_client_type()->CopyFrom(client_type_); |
| switch (state_) { |
| case State_NO_UNIQUIFIER_OR_SESSION: |
| // We need a client id, so make a request that will get a client id. |
| // Set message type to TYPE_ASSIGN_CLIENT_ID. |
| |
| // Generate a nonce if we haven't already done so. |
| if (nonce_ == -1) { |
| nonce_ = resources_->current_time().ToInternalValue(); |
| } |
| message->mutable_app_client_id()->set_string_value(app_client_id_); |
| // Set the nonce, action, and message type. |
| message->set_nonce(nonce_); |
| message->set_action(ClientToServerMessage_Action_ASSIGN_CLIENT_ID); |
| message->set_message_type( |
| ClientToServerMessage_MessageType_TYPE_ASSIGN_CLIENT_ID); |
| // Record the time that we're making the request, and increment the count |
| // for the number of session requests. |
| last_send_time_ = resources_->current_time(); |
| ++session_attempt_count_; |
| break; |
| |
| case State_UNIQUIFIER_ONLY: |
| // We need a session, so make a request that will get a session token. |
| // Set message type to TYPE_UPDATE_SESSION. |
| message->set_client_uniquifier(uniquifier_); |
| message->set_action(ClientToServerMessage_Action_UPDATE_SESSION); |
| message->set_message_type( |
| ClientToServerMessage_MessageType_TYPE_UPDATE_SESSION); |
| last_send_time_ = resources_->current_time(); |
| ++session_attempt_count_; |
| break; |
| |
| case State_UNIQUIFIER_AND_SESSION: |
| // Set the session token but leave the message type unset; the |
| // registration manager will decide the proper message type. |
| message->set_session_token(session_token_); |
| break; |
| |
| default: |
| CHECK(false); |
| } |
| } |
| |
| MessageAction SessionManager::ProcessMessage( |
| const ServerToClientMessage& message) { |
| |
| if (!message.has_message_type()) { |
| TLOG(WARNING_LEVEL, "Ignoring message with no type"); |
| return IGNORE_MESSAGE; |
| } |
| |
| if (!version_manager_.ProtocolVersionSupported(message)) { |
| TLOG(WARNING_LEVEL, "Ignoring message with unsupported version"); |
| return IGNORE_MESSAGE; |
| } |
| |
| ServerToClientMessage_MessageType msg_type = message.message_type(); |
| TLOG(INFO_LEVEL, "Process message with type %d", msg_type); |
| switch (msg_type) { |
| case ServerToClientMessage_MessageType_TYPE_ASSIGN_CLIENT_ID: |
| return ProcessAssignClientId(message); |
| |
| case ServerToClientMessage_MessageType_TYPE_UPDATE_SESSION: |
| return ProcessUpdateSession(message); |
| |
| case ServerToClientMessage_MessageType_TYPE_INVALIDATE_CLIENT_ID: |
| return ProcessInvalidateClientId(message); |
| |
| case ServerToClientMessage_MessageType_TYPE_INVALIDATE_SESSION: |
| return ProcessInvalidateSession(message); |
| |
| case ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL: |
| // Delegate should-process check. |
| return CheckObjectControlMessage(message); |
| |
| default: |
| // Unrecognizable message. |
| TLOG(WARNING_LEVEL, "Unknown message type: %d", msg_type); |
| return IGNORE_MESSAGE; |
| } |
| } |
| |
| MessageAction SessionManager::ProcessAssignClientId( |
| const ServerToClientMessage& message) { |
| // SUCCESS status required by spec. |
| // TODO: handle PERMANENT_FAILURE. |
| if (message.status().code() != Status_Code_SUCCESS) { |
| TLOG(WARNING_LEVEL, |
| "Ignoring assign-client-id message with non-success response: %d", |
| message.status().code()); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Ignore the message if we have an id. |
| if (!uniquifier_.empty()) { |
| TLOG(INFO_LEVEL, "Ignoring assign-client-id message: Ticl has an id"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Ignore the message if we don't have a nonce -- presence of a nonce |
| // indicates that we're expecting an id. |
| if (nonce_ == -1) { |
| TLOG(INFO_LEVEL, "Ignoring assign-client-id message: Ticl has no nonce"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Ignore the message if the message does not have a nonce in it. |
| if (!message.has_nonce()) { |
| TLOG(WARNING_LEVEL, |
| "Ignoring purported assign-client-id message with no nonce"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Ignore the message if its nonce does not match our nonce. |
| if (nonce_ != static_cast<int64>(message.nonce())) { |
| TLOG(INFO_LEVEL, |
| "Ignoring assign-client-id message with non-matching nonce: " |
| "%lld vs %lld", nonce_, message.nonce()); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Ignore the message if it does not have a client id and session token. |
| if (!(message.has_session_token() && message.has_client_uniquifier())) { |
| TLOG(WARNING_LEVEL, "Ignoring purported assign-client-id with a missing " |
| "client id or session"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Ignore the message if it has an empty client id or session token. |
| // This check prevents us from accepting obviously-wrong data. |
| if (message.session_token().empty() || message.client_uniquifier().empty()) { |
| TLOG(WARNING_LEVEL, "Ignoring purported assign-client-id with a empty " |
| "client id or session"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Ignore the message if its client type and app client id do not match ours. |
| bool client_type_matches = |
| message.client_type().type() == client_type_.type(); |
| bool app_client_id_matches = |
| message.app_client_id().string_value() == app_client_id_; |
| if (!(client_type_matches && app_client_id_matches)) { |
| TLOG(INFO_LEVEL, |
| "Ignoring assign-client-id message with non-matching client type or " |
| "app-client id"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Message passes verification. Acquire the client id and session token |
| // from the message. Clear our nonce. |
| TLOG(INFO_LEVEL, "Accepting assign-client-id request"); |
| session_token_ = message.session_token(); |
| uniquifier_ = message.client_uniquifier(); |
| nonce_ = -1; |
| UpdateState(); |
| |
| // Reset the count of unsuccessful session acquisition attempts. |
| session_attempt_count_ = 0; |
| |
| return ACQUIRE_SESSION; |
| } |
| |
| MessageAction SessionManager::ProcessUpdateSession( |
| const ServerToClientMessage& message) { |
| // SUCCESS status required by spec. |
| if (message.status().code() != Status_Code_SUCCESS) { |
| TLOG(WARNING_LEVEL, |
| "Ignoring update-session message with non-success response"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // If we don't have a client id, we can't accept a new session. |
| if (uniquifier_.empty()) { |
| TLOG(INFO_LEVEL, "Ignoring update-session since Ticl has no client id"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // If the message does not have a client id, we can't process it. |
| if (!message.has_client_uniquifier()) { |
| TLOG(WARNING_LEVEL, "Ignoring purported update-session with no client id"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // If the message does not have a session, we can't process it. |
| // We check sessionToken == '' to avoid taking an obviously-wrong token. |
| if (!message.has_session_token() || message.session_token().empty()) { |
| TLOG(WARNING_LEVEL, "Ignoring purported update-session with no session"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // We accept the new session if the client id in the message matches our own. |
| if (message.client_uniquifier() == uniquifier_) { |
| TLOG(INFO_LEVEL, "Accepting new session %s replacing old session %s", |
| message.session_token().c_str(), session_token_.c_str()); |
| session_token_ = message.session_token(); |
| UpdateState(); |
| // Reset the count of unsuccessful session acquisition attempts. |
| session_attempt_count_ = 0; |
| return ACQUIRE_SESSION; |
| } |
| return IGNORE_MESSAGE; |
| } |
| |
| MessageAction SessionManager::ProcessInvalidateClientId( |
| const ServerToClientMessage& message) { |
| // UNKNOWN_CLIENT status required by spec. |
| if (message.status().code() != Status_Code_UNKNOWN_CLIENT) { |
| TLOG(WARNING_LEVEL, |
| "Ignoring invalidate-client-id msg with non-UNKNOWN_CLIENT response"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // We cannot invalidate our client id if we do not have one. |
| if (uniquifier_.empty()) { |
| TLOG(INFO_LEVEL, |
| "Ignoring invalidate-client-id message since the Ticl has no id"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Invalidate our client id if the client id in the message matches ours. |
| if (uniquifier_ == message.client_uniquifier()) { |
| return LOSE_CLIENT_ID; |
| } else { |
| TLOG(INFO_LEVEL, "Ignoring invalidate-client with mis-matching client id"); |
| return IGNORE_MESSAGE; |
| } |
| } |
| |
| void SessionManager::DoLoseClientId() { |
| TLOG(INFO_LEVEL, "Client id invalidated"); |
| uniquifier_.clear(); |
| session_token_.clear(); |
| // Set the "last send time" into the far past so we'll be allowed to send an |
| // assign-client-id request at least once. |
| last_send_time_ = Time() - TimeDelta::FromHours(1); |
| UpdateState(); |
| } |
| |
| MessageAction SessionManager::ProcessInvalidateSession( |
| const ServerToClientMessage& message) { |
| // INVALID_SESSION status required by spec. |
| if (message.status().code() != Status_Code_INVALID_SESSION) { |
| TLOG(WARNING_LEVEL, |
| "Ignoring invalidate-session msg with non-INVALID_SESSION response"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // If we do not have a session, we cannot invalidate it. |
| if (session_token_.empty()) { |
| TLOG(INFO_LEVEL, |
| "Ignoring invalide-session message since Ticl has no session"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // If the message does not have a session, we cannot invalidate ours. |
| if (!message.has_session_token()) { |
| TLOG(WARNING_LEVEL, |
| "Ignoring purported invalidate-session message with no session token"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // Invalidate our session token if it matches the one in the message. |
| if (session_token_ == message.session_token()) { |
| TLOG(INFO_LEVEL, "Invalidating session: %s", session_token_.c_str()); |
| session_token_.clear(); |
| // Set the "last send time" into the far past so we'll be allowed to send an |
| // update-session request at least once. |
| last_send_time_ = Time() - TimeDelta::FromHours(1); |
| UpdateState(); |
| return LOSE_SESSION; |
| } |
| return IGNORE_MESSAGE; |
| } |
| |
| MessageAction SessionManager::CheckObjectControlMessage( |
| const ServerToClientMessage& message) { |
| // SUCCESS status required by spec. |
| if (message.status().code() != Status_Code_SUCCESS) { |
| TLOG(WARNING_LEVEL, |
| "Ignoring object-control message with non-success response"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // If we don't have a valid session and client, we cannot process the |
| // message. Technically, sessionToken != null implies clientId != null, but |
| // we'll be defensive and check both. |
| if (session_token_.empty() || uniquifier_.empty()) { |
| TLOG(INFO_LEVEL, |
| "Ignoring OBJECT_CONTROL message since Ticl does not have a session"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // If the message doesn't have a session, we cannot process it. |
| if (!message.has_session_token()) { |
| TLOG(WARNING_LEVEL, "Received purported OBJECT_CONTROL message with no " |
| "session token; ignoring"); |
| return IGNORE_MESSAGE; |
| } |
| |
| // We have a session and the message has a session. We process the message if |
| // the sessions match. |
| if (session_token_ == message.session_token()) { |
| return PROCESS_OBJECT_CONTROL; |
| } else { |
| TLOG(INFO_LEVEL, "session token mismatch: %s vs %s", session_token_.c_str(), |
| message.session_token().c_str()); |
| return IGNORE_MESSAGE; |
| } |
| } |
| |
| bool SessionManager::HasDataToSend() { |
| // If we haven't sent anything in a very long time, reset the session attempt |
| // counter. |
| Time now = resources_->current_time(); |
| if (now - last_send_time_ > |
| TimeDelta::FromMinutes(kWakeUpAfterGiveUpIntervalMinutes)) { |
| session_attempt_count_ = 0; |
| } |
| // The session manager only needs to send data if we don't have a session. |
| // It's only allowed to do so if it (a) hasn't just sent a request, and (b) |
| // hasn't tried more than kMaxSessionAttempts times without getting a |
| // successful response. |
| return !HasSession() && |
| (now > last_send_time_ + config_.registration_timeout) && |
| (session_attempt_count_ < kMaxSessionAttempts); |
| } |
| |
| const int SessionManager::kMaxSessionAttempts = 5; |
| const int SessionManager::kWakeUpAfterGiveUpIntervalMinutes = 3 * 60; |
| |
| } // namespace invalidation |