/** * Copyright 2012 José Martínez * * 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 es.udc.pfc.xmpp.handler; import static com.google.common.base.Preconditions.checkNotNull; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; import org.jboss.netty.util.CharsetUtil; import com.google.common.hash.Hashing; import es.udc.pfc.xmpp.stanza.Stanza; import es.udc.pfc.xmpp.stanza.XMPPNamespaces; import es.udc.pfc.xmpp.xml.XMLElement; /** * XEP-0114 Stream Decoder. */ public class XEP0114Decoder extends SimpleChannelHandler { private static final QName STREAM_NAME = new QName(XMPPNamespaces.STREAM, "stream", "stream"); private static enum Status { CONNECT, AUTHENTICATE, READY, DISCONNECTED; } private final String serverName; private final String secret; private Status status; private String streamID; public XEP0114Decoder(String serverName, String secret) throws XMLStreamException { super(); this.serverName = checkNotNull(serverName); this.secret = checkNotNull(secret); status = Status.CONNECT; } @Override public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { Channels.write(ctx.getChannel(), ChannelBuffers.copiedBuffer("<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='" + serverName + "'>", CharsetUtil.UTF_8)); //ctx.sendUpstream(e); } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { if (e.getMessage() instanceof XMLEvent) { final XMLEvent event = (XMLEvent) e.getMessage(); switch (status) { case CONNECT: if (event.isStartElement()) { final StartElement element = event.asStartElement(); if (STREAM_NAME.equals(element.getName()) && XMPPNamespaces.ACCEPT.equals(element.getNamespaceURI(null))) { if (!serverName.equals(element.getAttributeByName(new QName("from")).getValue())) { throw new Exception("server name mismatch"); } streamID = element.getAttributeByName(new QName("id")).getValue(); status = Status.AUTHENTICATE; Channels.write(ctx.getChannel(), ChannelBuffers.copiedBuffer("<handshake>" + Hashing.sha1().hashString(streamID + secret, CharsetUtil.UTF_8).toString() + "</handshake>", CharsetUtil.UTF_8)); } } else { throw new Exception("Expected stream:stream element"); } break; case AUTHENTICATE: case READY: if (event.isEndElement()) { final EndElement element = event.asEndElement(); if (STREAM_NAME.equals(element.getName())) { Channels.disconnect(ctx.getChannel()); return; } } break; case DISCONNECTED: throw new Exception("received DISCONNECTED"); } } else if (e.getMessage() instanceof XMLElement) { final XMLElement element = (XMLElement) e.getMessage(); switch (status) { case AUTHENTICATE: if (!"handshake".equals(element.getTagName())) throw new Exception("expected handshake"); status = Status.READY; System.out.println("logged in"); ctx.getPipeline().get(XMPPStreamHandler.class).loggedIn(); break; case READY: final Stanza stanza = Stanza.fromElement(element); if (stanza == null) throw new Exception("Unknown stanza"); Channels.fireMessageReceived(ctx, stanza); break; default: throw new Exception("unexpected handleElement"); } } else { ctx.sendUpstream(e); } } @Override public void disconnectRequested(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { Channels.write(ctx, e.getFuture(), ChannelBuffers.copiedBuffer("</stream:stream>", CharsetUtil.UTF_8)); } }