All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.smack.PacketReader Maven / Gradle / Ivy

The newest version!
/**
 *
 * Copyright 2003-2007 Jive Software.
 *
 * 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 org.jivesoftware.smack;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.dom4j.Element;
import org.dom4j.io.XPPPacketReader;
import org.jamppa.client.plugin.Plugin;
import org.jivesoftware.smack.Connection.ListenerWrapper;
import org.jivesoftware.smack.packet.Authentication;
import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.ping.packet.Ping;
import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
import org.jivesoftware.smack.sasl.SASLMechanism.Success;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.PacketError.Condition;
import org.xmpp.packet.Presence;
import org.xmpp.packet.Roster;

/**
 * Listens for XML traffic from the XMPP server and parses it into packet
 * objects. The packet reader also invokes all packet listeners and collectors.
 * 

* * @see Connection#createPacketCollector * @see Connection#addPacketListener * @author Matt Tucker */ class PacketReader { private static Logger LOGGER = Logger.getLogger(PacketReader.class .getName()); private Thread readerThread; private ExecutorService listenerExecutor; private XMPPConnection connection; private XPPPacketReader innerReader; private boolean reset = false; volatile boolean done; private String connectionID = null; protected PacketReader(final XMPPConnection connection) { this.connection = connection; this.init(); } /** * Initializes the reader in order to be used. The reader is initialized * during the first connection and when reconnecting due to an abruptly * disconnection. */ protected void init() { done = false; connectionID = null; readerThread = new Thread() { public void run() { parsePackets(this); } }; readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")"); readerThread.setDaemon(true); // Create an executor to deliver incoming packets to listeners. We'll // use a single // thread with an unbounded queue. listenerExecutor = Executors .newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable, "Smack Listener Processor (" + connection.connectionCounterValue + ")"); thread.setDaemon(true); return thread; } }); resetParser(); } /** * Starts the packet reader thread and returns once a connection to the * server has been established. A connection will be attempted for a maximum * of five seconds. An XMPPException will be thrown if the connection fails. * * @throws XMPPException * if the server fails to send an opening stream back for more * than five seconds. */ synchronized public void startup() throws XMPPException { final List errors = new LinkedList(); AbstractConnectionListener connectionErrorListener = new AbstractConnectionListener() { @Override public void connectionClosedOnError(Exception e) { errors.add(e); } }; connection.addConnectionListener(connectionErrorListener); readerThread.start(); // Wait for stream tag before returning. We'll wait a couple of seconds // before // giving up and throwing an error. try { // A waiting thread may be woken up before the wait time or a notify // (although this is a rare thing). Therefore, we continue waiting // until either a connectionID has been set (and hence a notify was // made) or the total wait time has elapsed. int waitTime = SmackConfiguration.getPacketReplyTimeout(); wait(3 * waitTime); } catch (InterruptedException ie) { // Ignore. } connection.removeConnectionListener(connectionErrorListener); if (connectionID == null) { throw new XMPPException( "Connection failed. No response from server."); } else if (!errors.isEmpty()) { throw new XMPPException(errors.iterator().next()); } else { connection.connectionID = connectionID; } } /** * Shuts the packet reader down. */ public void shutdown() { // Notify connection listeners of the connection closing if done hasn't // already been set. if (!done) { for (ConnectionListener listener : connection .getConnectionListeners()) { try { listener.connectionClosed(); } catch (Exception e) { // Catch and print any exception so we can recover // from a faulty listener and finish the shutdown process LOGGER.log(Level.ERROR, "Error in listener while closing connection", e); } } } done = true; // Shut down the listener executor. listenerExecutor.shutdown(); } /** * Resets the parser using the latest connection's reader. Reseting the * parser is necessary when the plain connection has been secured or when a * new opening stream element is going to be sent by the server. */ private void resetParser() { try { innerReader = new XPPPacketReader(); innerReader.setXPPFactory(XmlPullParserFactory.newInstance()); innerReader.getXPPParser().setInput(connection.reader); reset = true; } catch (Exception xppe) { LOGGER.log(Level.WARN, "Error while resetting parser", xppe); } } private void startStream() throws XmlPullParserException, IOException { XmlPullParser xpp = innerReader.getXPPParser(); for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { eventType = xpp.next(); } parseStreamStart(xpp); } private void parseStreamStart(XmlPullParser parser) { if ("jabber:client".equals(parser.getNamespace(null))) { for (int i = 0; i < parser.getAttributeCount(); i++) { if (parser.getAttributeName(i).equals("id")) { connectionID = parser.getAttributeValue(i); if (!"1.0".equals(parser.getAttributeValue("", "version"))) { releaseConnectionIDLock(); } } else if (parser.getAttributeName(i).equals("from")) { connection.config.setServiceName(parser .getAttributeValue(i)); } } } } /** * Parse top-level packets in order to process them further. * * @param thread * the thread that is being used by the reader to parse incoming * packets. */ private void parsePackets(Thread thread) { try { while (!done) { if (reset) { startStream(); LOGGER.debug("Started xmlstream..."); reset = false; continue; } Element doc = innerReader.parseDocument().getRootElement(); if (doc == null) { connection.disconnect(); LOGGER.debug("End of xmlstream."); continue; } Packet packet = null; LOGGER.debug("Processing packet " + doc.asXML()); packet = parseFromPlugins(doc, packet); if (packet == null) { packet = parseFromCore(doc); } if (packet != null) { processPacket(packet); } } } catch (Exception e) { if (!done && !connection.isSocketClosed()) { connection.notifyConnectionError(e); if (!connection.isConnected()) { releaseConnectionIDLock(); } } } } private Packet parseFromCore(Element doc) throws XMPPException, IOException, XmlPullParserException, Exception { String tag = doc.getName(); Packet packet = null; if ("message".equals(tag)) { packet = new Message(doc); } else if ("presence".equals(tag)) { packet = new Presence(doc); } else if ("iq".equals(tag)) { packet = parseIQ(doc); } else if ("error".equals(tag)) { throw new XMPPException(PacketParserUtils.parseStreamError(doc)); } else if ("features".equals(tag)) { parseFeatures(doc); } else if ("proceed".equals(tag)) { connection.proceedTLSReceived(); resetParser(); } else if ("failure".equals(tag)) { parseFailure(doc); } else if ("challenge".equals(tag)) { parseChallenge(doc); } else if ("success".equals(tag)) { parseSuccess(doc); } else if ("compressed".equals(tag)) { connection.startStreamCompression(); resetParser(); } else { throw new XmlPullParserException("Unknown packet type was read: " + tag); } return packet; } private Packet parseFromPlugins(Element doc, Packet packet) { for (Plugin plugin : connection.getPlugins()) { packet = plugin.parse(doc); if (packet != null) { break; } } return packet; } private void parseChallenge(Element doc) throws IOException { String challengeData = doc.getText(); processPacket(new Challenge(doc)); connection.getSASLAuthentication().challengeReceived(challengeData); } private void parseSuccess(Element doc) throws IOException { processPacket(new Success(doc)); connection.packetWriter.openStream(); resetParser(); connection.getSASLAuthentication().authenticated(); } private void parseFailure(Element failureEl) throws Exception { if ("urn:ietf:params:xml:ns:xmpp-tls".equals(failureEl.getNamespace() .getURI())) { throw new Exception("TLS negotiation has failed"); } else if ("http://jabber.org/protocol/compress".equals(failureEl .getNamespace().getURI())) { connection.streamCompressionDenied(); } else { final Failure failure = new Failure(failureEl); processPacket(failure); connection.getSASLAuthentication().authenticationFailed( failure.getCondition()); } } private IQ parseIQ(Element doc) { Element pingEl = doc.element(Ping.ELEMENT); if (pingEl != null && pingEl.getNamespace().getURI().equals(Ping.NAMESPACE)) { return new Ping(doc); } Element bindEl = doc.element(Bind.ELEMENT); if (bindEl != null && bindEl.getNamespace().getURI().equals(Bind.NAMESPACE)) { return new Bind(doc); } Element query = doc.element("query"); if (query != null) { if ("jabber:iq:roster".equals(query.getNamespaceURI())) { return new Roster(doc); } if ("jabber:iq:auth".equals(query.getNamespaceURI())) { return new Authentication(doc); } } return new IQ(doc); } /** * Releases the connection ID lock so that the thread that was waiting can * resume. The lock will be released when one of the following three * conditions is met: *

* * 1) An opening stream was sent from a non XMPP 1.0 compliant server 2) * Stream features were received from an XMPP 1.0 compliant server that does * not support TLS 3) TLS negotiation was successful * */ synchronized private void releaseConnectionIDLock() { notify(); } /** * Processes a packet after it's been fully parsed by looping through the * installed packet collectors and listeners and letting them examine the * packet to see if they are a match with the filter. * * @param packet * the packet to process. */ private void processPacket(Packet packet) { if (packet == null) { return; } // Loop through all collectors and notify the appropriate ones. for (PacketCollector collector : connection.getPacketCollectors()) { collector.processPacket(packet); } // Deliver the incoming packet to listeners. listenerExecutor.submit(new ListenerNotification(packet)); } private void parseFeatures(Element doc) throws Exception { boolean startTLSReceived = false; Element startTLSEl = doc.element("starttls"); if (startTLSEl != null && startTLSEl.getNamespace().getURI() .equals("urn:ietf:params:xml:ns:xmpp-tls")) { startTLSReceived = true; connection.startTLSReceived(startTLSEl.element("required") != null); } Element mechanismsEl = doc.element("mechanisms"); if (mechanismsEl != null && mechanismsEl.getNamespace().getURI() .equals("urn:ietf:params:xml:ns:xmpp-sasl")) { connection.getSASLAuthentication().setAvailableSASLMethods( PacketParserUtils.parseMechanisms(mechanismsEl)); } Element bindEl = doc.element("bind"); if (bindEl != null && bindEl.getNamespace().getURI() .equals("urn:ietf:params:xml:ns:xmpp-bind")) { connection.getSASLAuthentication().bindingRequired(); } Element cEl = doc.element("c"); if (cEl != null && cEl.getNamespace().getURI() .equals("http://jabber.org/protocol/caps")) { String node = doc.attributeValue("node"); String ver = doc.attributeValue("ver"); if (ver != null && node != null) { String capsNode = node + "#" + ver; connection.setServiceCapsNode(capsNode); } } Element sessionEl = doc.element("session"); if (sessionEl != null && sessionEl.getNamespace().getURI() .equals("urn:ietf:params:xml:ns:xmpp-session")) { connection.getSASLAuthentication().sessionsSupported(); } Element verEl = doc.element("ver"); if (verEl != null && verEl.getNamespace().getURI() .equals("urn:xmpp:features:rosterver")) { connection.setRosterVersioningSupported(); } Element compressionEl = doc.element("compression"); if (compressionEl != null && compressionEl.getNamespace().getURI() .equals("http://jabber.org/features/compress")) { connection.setAvailableCompressionMethods(PacketParserUtils .parseCompressionMethods(compressionEl)); } for (Plugin plugin : connection.getPlugins()) { plugin.checkSupport(doc); } if (!connection.isSecureConnection()) { if (!startTLSReceived && connection.getConfiguration().getSecurityMode() == ConnectionConfiguration.SecurityMode.required) { throw new XMPPException( "Server does not support security (TLS), " + "but security required by connection configuration.", new PacketError(Condition.forbidden)); } } if (!startTLSReceived || connection.getConfiguration().getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) { releaseConnectionIDLock(); } } /** * A runnable to notify all listeners of a packet. */ private class ListenerNotification implements Runnable { private Packet packet; public ListenerNotification(Packet packet) { this.packet = packet; } public void run() { for (ListenerWrapper listenerWrapper : connection.recvListeners .values()) { try { listenerWrapper.notifyListener(packet); } catch (Exception e) { LOGGER.log(Level.ERROR, "Exception in packet listener", e); } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy