/** * * Copyright 2003-2007 Jive Software. * * 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 org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.parsing.ParsingExceptionCallback; import org.jivesoftware.smack.parsing.UnparsablePacket; import org.jivesoftware.smack.sasl.SASLMechanism.Challenge; import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure; import org.jivesoftware.smack.sasl.SASLMechanism.Success; import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.SecurityRequiredException; import org.jivesoftware.smack.XMPPException.StreamErrorException; import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; /** * 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 XMPPConnection#createPacketCollector * @see XMPPConnection#addPacketListener * @author Matt Tucker */ class PacketReader { private Thread readerThread; private XMPPTCPConnection connection; private XmlPullParser parser; /** * Set to true if the last features stanza from the server has been parsed. A XMPP connection * handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature * stanza is send by the server. This is set to true once the last feature stanza has been * parsed. */ private volatile boolean lastFeaturesParsed; volatile boolean done; protected PacketReader(final XMPPTCPConnection connection) throws XmlPullParserException { 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. * * @throws XmlPullParserException if the parser could not be reset. */ protected void init() throws XmlPullParserException { done = false; lastFeaturesParsed = false; readerThread = new Thread() { public void run() { parsePackets(this); } }; readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")"); readerThread.setDaemon(true); resetParser(); } /** * Starts the packet reader thread and returns once a connection to the server * has been established or if the server's features could not be parsed within * the connection's PacketReplyTimeout. * * @throws NoResponseException if the server fails to send an opening stream back * within packetReplyTimeout. */ synchronized public void startup() throws NoResponseException { 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 the server's features have been parsed (and hence a notify was // made) or the total wait time has elapsed. wait(connection.getPacketReplyTimeout()); } catch (InterruptedException ie) { // Ignore. } if (!lastFeaturesParsed) { throw new NoResponseException(); } } /** * Shuts the packet reader down. */ public void shutdown() { // Notify connection listeners of the connection closing if done hasn't already been set. if (!done) { connection.callConnectionClosedListener(); } done = true; } /** * 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. * * @throws XmlPullParserException XmlPullParserException if the parser could not be reset. */ private void resetParser() throws XmlPullParserException { parser = XmlPullParserFactory.newInstance().newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(connection.reader); } /** * 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 { int eventType = parser.getEventType(); do { if (eventType == XmlPullParser.START_TAG) { int parserDepth = parser.getDepth(); ParsingExceptionCallback callback = connection.getParsingExceptionCallback(); if (parser.getName().equals("message")) { Packet packet; try { packet = PacketParserUtils.parseMessage(parser); } catch (Exception e) { String content = PacketParserUtils.parseContentDepth(parser, parserDepth); UnparsablePacket message = new UnparsablePacket(content, e); if (callback != null) { callback.handleUnparsablePacket(message); } continue; } connection.processPacket(packet); } else if (parser.getName().equals("iq")) { IQ iq; try { iq = PacketParserUtils.parseIQ(parser, connection); } catch (Exception e) { String content = PacketParserUtils.parseContentDepth(parser, parserDepth); UnparsablePacket message = new UnparsablePacket(content, e); if (callback != null) { callback.handleUnparsablePacket(message); } continue; } connection.processPacket(iq); } else if (parser.getName().equals("presence")) { Presence presence; try { presence = PacketParserUtils.parsePresence(parser); } catch (Exception e) { String content = PacketParserUtils.parseContentDepth(parser, parserDepth); UnparsablePacket message = new UnparsablePacket(content, e); if (callback != null) { callback.handleUnparsablePacket(message); } continue; } connection.processPacket(presence); } // We found an opening stream. Record information about it, then notify // the connectionID lock so that the packet reader startup can finish. else if (parser.getName().equals("stream")) { // Ensure the correct jabber:client namespace is being used. if ("jabber:client".equals(parser.getNamespace(null))) { // Get the connection id. for (int i=0; i