/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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 net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive; import java.util.*; import net.java.sip.communicator.impl.protocol.jabber.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.provider.*; import org.jivesoftware.smack.util.*; /** * XEP-0199: XMPP Ping. Tracks received packets and if for some interval * there is nothing received. * * @author Damian Minkov */ public class KeepAliveManager implements RegistrationStateChangeListener, PacketListener { /** * Our class logger */ private static final Logger logger = Logger.getLogger(KeepAliveManager.class); /** * The task sending packets */ private KeepAliveSendTask keepAliveSendTask = null; /** * The timer executing tasks on specified intervals */ private Timer keepAliveTimer; /** * The last received packet from server. */ private long lastReceiveActivity = 0; /** * The interval between checks. */ private int keepAliveCheckInterval; /** * If we didn't receive a packet between two checks, we send a packet, * so we can receive something error or reply. */ private String waitingForPacketWithID = null; /** * Our parent provider. */ private ProtocolProviderServiceJabberImpl parentProvider = null; /** * Creates manager. * @param parentProvider the parent provider. */ public KeepAliveManager(ProtocolProviderServiceJabberImpl parentProvider) { this.parentProvider = parentProvider; this.parentProvider.addRegistrationStateChangeListener(this); // register the KeepAlive Extension in the smack library // used only if somebody ping us ProviderManager.getInstance() .addIQProvider(KeepAliveEvent.ELEMENT_NAME, KeepAliveEvent.NAMESPACE, new KeepAliveEventProvider()); } /** * The method is called by a ProtocolProvider implementation whenever * a change in the registration state of the corresponding provider had * occurred. * @param evt ProviderStatusChangeEvent the event describing the status * change. */ public void registrationStateChanged(RegistrationStateChangeEvent evt) { if (logger.isDebugEnabled()) logger.debug("The provider changed state from: " + evt.getOldState() + " to: " + evt.getNewState()); if (evt.getNewState() == RegistrationState.REGISTERED) { parentProvider.getConnection().removePacketListener(this); parentProvider.getConnection().addPacketListener(this, null); // if for some unknown reason we got two registered events // and we have already created those tasks make sure we cancel them if(keepAliveSendTask != null) { logger.error("Those task is not supposed to be available for " + parentProvider.getAccountID().getDisplayName()); keepAliveSendTask.cancel(); keepAliveSendTask = null; } if(keepAliveTimer != null) { logger.error("Those timer is not supposed to be available for " + parentProvider.getAccountID().getDisplayName()); keepAliveTimer.cancel(); keepAliveTimer = null; } keepAliveSendTask = new KeepAliveSendTask(); waitingForPacketWithID = null; keepAliveCheckInterval = SmackConfiguration.getKeepAliveInterval(); if(keepAliveCheckInterval == 0) keepAliveCheckInterval = 30000; keepAliveTimer = new Timer("Jabber keepalive timer for <" + parentProvider.getAccountID() + ">", true); keepAliveTimer.scheduleAtFixedRate( keepAliveSendTask, keepAliveCheckInterval, keepAliveCheckInterval); } else if(evt.getNewState() == RegistrationState.UNREGISTERED || evt.getNewState() == RegistrationState.CONNECTION_FAILED || evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED) { waitingForPacketWithID = null; if(parentProvider.getConnection() != null) parentProvider.getConnection().removePacketListener(this); if(keepAliveSendTask != null) { keepAliveSendTask.cancel(); keepAliveSendTask = null; } if(keepAliveTimer != null) { keepAliveTimer.cancel(); keepAliveTimer = null; } } } /** * A packet Listener for all incoming packets. * @param packet an incoming packet */ public void processPacket(Packet packet) { // store that we have received lastReceiveActivity = System.currentTimeMillis(); if(waitingForPacketWithID != null && waitingForPacketWithID.equals(packet.getPacketID())) { // we are no more waiting for this packet waitingForPacketWithID = null; } if(packet instanceof KeepAliveEvent) { // replay only to server pings, to avoid leak of presence // skip if this is reply with error (like not supported) // we can still receive errors, but as this is keep-alive ping // we don't care for errors, as packets has already done its goal KeepAliveEvent evt = (KeepAliveEvent)packet; if(evt.getFrom() != null && evt.getFrom() .equals(parentProvider.getAccountID().getService()) && evt.getError() == null) { parentProvider.getConnection().sendPacket( IQ.createResultIQ(evt)); } } } /** * Task sending packets on intervals. * The task is runned on specified intervals by the keepAliveTimer */ private class KeepAliveSendTask extends TimerTask { /** * Minimal sleep interval between calls to this <tt>TimerTask</tt>. */ private final long MIN_WAKE_UP_INTERVAL = 5000L; // 5 sec /** * Remembers when it was woken up for the last time. */ private long lastWakeUp; /** * Sends a single <tt>KeepAliveEvent</tt>. */ @Override public void run() { /** * Timers on Android when CPU is sleeping can often sleep for too * long and then they try to catch up doing rapid callbacks. * The intention here is to filter out such calls. */ long sleepDuration = (System.currentTimeMillis() - lastWakeUp); lastWakeUp = System.currentTimeMillis(); if(sleepDuration < MIN_WAKE_UP_INTERVAL) { logger.error( this + " woken up too early !"); return; } // if we are not registered do nothing if(!parentProvider.isRegistered()) { if (logger.isTraceEnabled()) logger.trace("provider not registered. " +"won't send keep alive for " + parentProvider.getAccountID().getDisplayName()); parentProvider.unregisterInternal(false); parentProvider.fireRegistrationStateChanged( parentProvider.getRegistrationState(), RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, null); return; } if(System.currentTimeMillis() - lastReceiveActivity > keepAliveCheckInterval) { if(waitingForPacketWithID != null) { logger.error("un-registering not received ping packet " + "for: " + parentProvider.getAccountID().getDisplayName()); parentProvider.unregisterInternal(false); parentProvider.fireRegistrationStateChanged( parentProvider.getRegistrationState(), RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, null); return; } try { // lets send a ping to our service name, // the @service-name part of the user id KeepAliveEvent ping = new KeepAliveEvent( parentProvider.getOurJID(), StringUtils.parseServer( parentProvider.getAccountID().getAccountAddress())); if (logger.isTraceEnabled()) logger.trace("send keepalive for acc: " + parentProvider.getAccountID().getDisplayName()); waitingForPacketWithID = ping.getPacketID(); parentProvider.getConnection().sendPacket(ping); } catch(Throwable t) { logger.error("Error sending ping request!", t); waitingForPacketWithID = null; } } } } }