com.voxbone.kelpie.Session.java Source code

Java tutorial

Introduction

Here is the source code for com.voxbone.kelpie.Session.java

Source

/**
 *    Copyright 2012 Voxbone SA/NV
 *
 *  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 com.voxbone.kelpie;

import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.UUID;

import javax.sip.address.SipURI;
import javax.sip.DialogState;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.jabberstudio.jso.JID;
import org.jabberstudio.jso.JSOImplementation;
import org.jabberstudio.jso.NSI;
import org.jabberstudio.jso.Packet;
import org.jabberstudio.jso.Stream;
import org.jabberstudio.jso.StreamContext;
import org.jabberstudio.jso.StreamElement;
import org.jabberstudio.jso.StreamError;
import org.jabberstudio.jso.StreamException;
import org.jabberstudio.jso.event.PacketEvent;
import org.jabberstudio.jso.event.PacketListener;
import org.jabberstudio.jso.event.StreamStatusEvent;
import org.jabberstudio.jso.event.StreamStatusListener;
import org.jabberstudio.jso.io.src.ChannelStreamSource;
import org.jabberstudio.jso.util.Utilities;

/**
 * Represents an authenticated Server to Server java connection
 * 
 * This class can also manage inbound Dialback challenge requests, 
 * (for chalanges we initate the DialbackSession is used instead)
 * 
 * There is one connection for to each server keplie is federated with (in each direction)
 */
class Session extends Thread implements StreamStatusListener, PacketListener {

    enum StreamType {
        RTP, RTCP, VRTP, VRTCP
    }

    private Stream conn;
    private String host;
    private SocketChannel socketChannel;

    private String sessionKey;
    private boolean confirmed;

    private List<Packet> queue = new LinkedList<Packet>();

    private static String clientName = null;
    private static String clientVersion = null;
    private static String clientPriority;
    private static String statusNoteOnline;

    private static String fakeId = null;
    private static boolean featVID = true;
    private static boolean featPMUC = true;
    private static boolean featPMUC_UNI = false;
    private static boolean featSMS = false;
    private static boolean featPING = false;
    private static boolean featNICK = false;
    private static boolean subscribeEmu = false;

    private static boolean clientJingle = false;

    private static boolean useDtmfInfo = false;
    private static int dtmfDuration;

    private static String iconHash;
    private static byte[] iconData;
    private static int iconSize;

    static Logger logger = Logger.getLogger(Session.class);

    public String internalCallId;

    private static String convertToHex(byte[] data) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < data.length; i++) {
            int halfbyte = (data[i] >>> 4) & 0x0F;
            int two_halfs = 0;
            do {
                if ((0 <= halfbyte) && (halfbyte <= 9)) {
                    buf.append((char) ('0' + halfbyte));
                } else {
                    buf.append((char) ('a' + (halfbyte - 10)));
                }
                halfbyte = data[i] & 0x0F;
            } while (two_halfs++ < 1);
        }
        return buf.toString();
    }

    static {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-1");

            byte[] sha1hash = new byte[40];
            byte[] data = new byte[20000];
            InputStream is = Session.class.getResourceAsStream("/icon.jpg");
            iconSize = is.read(data, 0, data.length);
            iconData = new byte[iconSize];
            System.arraycopy(data, 0, iconData, 0, iconSize);
            md.update(iconData);
            sha1hash = md.digest();
            iconHash = convertToHex(sha1hash);
        } catch (NoSuchAlgorithmException e) {

        } catch (IOException e) {
            logger.error("Error reading icon", e);
        } catch (Exception e) {
            logger.error("Error reading icon", e);
        }
    }

    public static void configure(Properties properties) {
        clientName = "http://" + properties.getProperty("com.voxbone.kelpie.hostname", "kelpie.voxbone.com")
                + "/caps";
        clientVersion = properties.getProperty("com.voxbone.kelpie.version", "git");
        clientPriority = properties.getProperty("com.voxbone.kelpie.feature.priority", "24");
        statusNoteOnline = properties.getProperty("com.voxbone.kelpie.status_note.online", "Kelpie Phone");
        fakeId = properties.getProperty("com.voxbone.kelpie.service_name", "kelpie");
        featVID = Boolean.parseBoolean(properties.getProperty("com.voxbone.kelpie.feature.video", "true"));
        featPMUC = Boolean.parseBoolean(properties.getProperty("com.voxbone.kelpie.feature.pmuc", "true"));
        featPMUC_UNI = Boolean
                .parseBoolean(properties.getProperty("com.voxbone.kelpie.feature.pmuc.unique", "false"));
        featSMS = Boolean.parseBoolean(properties.getProperty("com.voxbone.kelpie.feature.sms", "false"));
        featPING = Boolean.parseBoolean(properties.getProperty("com.voxbone.kelpie.feature.xmpp-ping", "false"));
        useDtmfInfo = Boolean.parseBoolean(properties.getProperty("com.voxbone.kelpie.feature.dtmf-info", "false"));
        dtmfDuration = Integer.parseInt(properties.getProperty("com.voxbone.kelpie.feature.dtmf-duration", "160"));
        featNICK = Boolean
                .parseBoolean(properties.getProperty("com.voxbone.kelpie.feature.chat-nickname", "false"));
        subscribeEmu = Boolean
                .parseBoolean(properties.getProperty("com.voxbone.kelpie.feature.subscribe.force-emu", "false"));
        clientJingle = Boolean.parseBoolean(properties.getProperty("com.voxbone.kelpie.jingle", "false"));

    }

    public static String getVersion() {
        return clientVersion;
    }

    private long idNum = 0;

    public Session(String internalCallId, String host, SocketChannel sc) throws StreamException {
        this.internalCallId = internalCallId;
        JSOImplementation jso = JSOImplementation.getInstance();
        this.socketChannel = sc;
        this.host = host;
        conn = jso.createStream(Utilities.SERVER_NAMESPACE);
        conn.getOutboundContext().addNamespace("db", "jabber:server:dialback");
        conn.getOutboundContext().setID(Integer.toHexString((int) (Math.random() * 1000000000)));

        conn.addStreamStatusListener(new StatusMonitor(this.internalCallId));
        conn.addStreamStatusListener(this);

        conn.addPacketListener(PacketEvent.RECEIVED, this);

        conn.connect(new ChannelStreamSource(sc));

        start();
    }

    public synchronized void sendPacket(Packet p) throws StreamException {
        if (!confirmed) {
            queue.add(p);
        } else {
            try {
                conn.send(p);
            } catch (StreamException e) {
                // ignore ...
            }
        }
    }

    public void sendDBResult(String to) throws StreamException {
        JID local = new JID(host);
        JID remote = new JID(to);

        conn.getOutboundContext().setTo(remote);

        SessionManager.addSession(this);

        conn.open();

        Packet p = conn.getDataFactory().createPacketNode(new NSI("result", "jabber:server:dialback"),
                Packet.class);

        p.setFrom(local);
        p.setTo(remote);
        sessionKey = UUID.randomUUID().toString();

        p.addText(sessionKey);
        conn.send(p);
    }

    public Stream getConnection() {
        return conn;
    }

    public String getSessionKey() {
        return sessionKey;
    }

    public void confirm() {
        confirmed = true;
    }

    public boolean isConfirmed() {
        return confirmed;
    }

    public boolean execute() throws Exception {
        boolean running = true;

        try {
            if (conn.getCurrentStatus().isConnected()) {
                conn.process();
            } else {
                conn.close();
                conn.disconnect();
            }
        } catch (StreamException e) {
            // ignore ...
            conn.close();
            conn.disconnect();
        }

        if (conn.getCurrentStatus().isDisconnected()) {
            running = false;
            SessionManager.removeSession(this);
        }

        return running;
    }

    private void handleInitate(Packet packet, boolean jingle) throws StreamException {
        logger.debug("[[" + internalCallId + "]] Got a request to start a call");
        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(), packet.getFrom());
        sess.ackIQ(packet);

        CallSession cs = new CallSession();
        logger.debug("[[" + internalCallId + "]] created call session : [[" + cs.internalCallId + "]]");
        cs.parseInitiate(packet, jingle);

        CallManager.addSession(cs);

        /* For coherence, we try to use the domain he has used in his subscription */
        String domain = host;

        SipSubscription sub = SipSubscriptionManager.getWatcher(UriMappings.toSipId(cs.jabberRemote),
                cs.jabberLocal.getNode());

        if (sub != null) {
            domain = ((SipURI) sub.remoteParty.getURI()).getHost();
        }
        SipService.sendInvite(cs, domain);
    }

    private void handleTransportList(Session sess, StreamElement session, CallSession cs) {
        for (Object objCandidate : session.listElements("candidate")) {
            StreamElement candidate = (StreamElement) objCandidate;

            if (candidate != null && candidate.getAttributeValue("protocol").equals("udp")) {

                if (cs != null) {
                    logger.debug("[[" + internalCallId + "]] got call session : [[" + cs.internalCallId + "]]");

                    if (candidate.getAttributeValue("name")
                            .equals("video_rtp")/* || candidate.getAttributeValue("name").equals("video_rtcp")*/) {
                        if (!cs.sentVTransport) {
                            sess.sendTransportCandidates(cs, StreamType.VRTP);
                        }

                        cs.vRelay.sendBind(candidate.getAttributeValue("username"), cs.candidateVUser,
                                candidate.getAttributeValue("address"),
                                Integer.parseInt(candidate.getAttributeValue("port")), false);
                    } else if (candidate.getAttributeValue("name").equals("video_rtcp")) {
                        if (!cs.sentVTransport) {
                            sess.sendTransportCandidates(cs, StreamType.VRTCP);
                        }

                        cs.vRelay.sendBind(candidate.getAttributeValue("username"), cs.candidateVUser,
                                candidate.getAttributeValue("address"),
                                Integer.parseInt(candidate.getAttributeValue("port")), true);
                    } else if (candidate.getAttributeValue("name")
                            .equals("rtp")/* || candidate.getAttributeValue("name").equals("rtcp")*/) {
                        if (!cs.sentTransport) {
                            sess.sendTransportCandidates(cs, StreamType.RTP);
                        }

                        cs.relay.sendBind(candidate.getAttributeValue("username"), cs.candidateUser,
                                candidate.getAttributeValue("address"),
                                Integer.parseInt(candidate.getAttributeValue("port")), false);
                    } else if (candidate.getAttributeValue("name").equals("rtcp")) {
                        if (!cs.sentTransport) {
                            sess.sendTransportCandidates(cs, StreamType.RTCP);
                        }

                        cs.relay.sendBind(candidate.getAttributeValue("username"), cs.candidateUser,
                                candidate.getAttributeValue("address"),
                                Integer.parseInt(candidate.getAttributeValue("port")), true);
                    }
                }
            }
        }

    }

    public void packetTransferred(PacketEvent evt) {
        try {
            JID local = evt.getData().getTo();
            JID remote = evt.getData().getFrom();

            logger.debug("[[" + internalCallId + "]] got message of " + evt.getData().getQualifiedName());

            if (evt.getData().getQualifiedName().equals("db:result")) {
                String type = evt.getData().getAttributeValue("type");
                if (type != null && type.length() > 0) {
                    synchronized (this) {
                        confirm();
                        while (!queue.isEmpty()) {
                            conn.send(queue.remove(0));
                        }
                    }
                    return;
                }

                String result = evt.getData().normalizeText();
                evt.setHandled(true);

                // this isn't a dialback connection - add it to the list of connections
                // we don't save this because it can only be used for inbound stuff
                //SessionManager.addSession(this);

                logger.debug("[[" + internalCallId + "]] Got a result response: " + evt.getData().normalizeText());
                logger.debug("[[" + internalCallId + "]] Packet is of type: " + evt.getData().getClass().getName());

                DialbackSession dbs = new DialbackSession(internalCallId, local, remote,
                        conn.getOutboundContext().getID(), result);

                boolean valid = dbs.doDialback();
                Packet p = null;
                if (valid) {
                    logger.debug("[[" + internalCallId + "]] Session is valid!");
                    p = conn.getDataFactory().createPacketNode(new NSI("result", "jabber:server:dialback"),
                            Packet.class);
                    p.setFrom(local);
                    p.setTo(remote);
                    p.setAttributeValue("type", "valid");
                    confirm();
                } else {
                    logger.debug("[[" + internalCallId + "]] Session is NOT valid!");
                    p = conn.getDataFactory().createPacketNode(new NSI("result", "jabber:server:dialback"),
                            Packet.class);
                    p.setFrom(local);
                    p.setTo(remote);
                    p.setAttributeValue("type", "invalid");
                }

                try {
                    conn.send(p);
                } catch (StreamException e) {
                    logger.error("Error sending packet!", e);
                }

                if (!valid) {
                    // close the stream if invalid
                    conn.close();
                }
            } else if (evt.getData().getQualifiedName().equals("db:verify")) {
                String key = evt.getData().normalizeText();
                // if we get a db:verify here and n
                logger.debug("[[" + internalCallId + "]] Got a verification token " + key);
                Session sess = SessionManager.getSession(evt.getData().getFrom());

                boolean valid = false;
                if (sess != null) {
                    logger.debug("[[" + internalCallId + "]] Found matching session");

                    if (key.equals(sess.getSessionKey())) {
                        logger.debug("[[" + internalCallId + "]] Keys Match! Sending the ok");
                        valid = true;
                    }
                }

                Packet p;
                if (valid) {
                    logger.debug("[[" + internalCallId + "]] Session is valid!");
                    p = conn.getDataFactory().createPacketNode(new NSI("verify", "jabber:server:dialback"),
                            Packet.class);
                    p.setFrom(local);
                    p.setTo(remote);
                    p.setID(evt.getData().getID());
                    p.setAttributeValue("type", "valid");
                } else {
                    logger.debug("[[" + internalCallId + "]] Session is NOT valid!");
                    p = conn.getDataFactory().createPacketNode(new NSI("verify", "jabber:server:dialback"),
                            Packet.class);
                    p.setFrom(local);
                    p.setTo(remote);
                    p.setAttributeValue("type", "invalid");
                }

                try {
                    conn.send(p);
                } catch (StreamException e) {
                    logger.error("Steam error in session", e);
                }

            } else if (evt.getData().getQualifiedName().equals(":message")
                    && evt.getData().getAttributeValue("type") != null)
            //&& evt.getData().getAttributeValue("type").equals("chat"))
            {
                if (evt.getData().getAttributeValue("type").equals("error")) {
                    logger.debug("[[" + internalCallId + "]] got an MESSAGE error ");
                    // should we notify this error to the sender?
                } else if (evt.getData().getAttributeValue("type").equals("chat")) {
                    logger.debug("[[" + internalCallId + "]] Got an IM");

                    StreamElement body = evt.getData().getFirstElement("body");
                    if (body != null) {
                        String msg = body.normalizeText();
                        logger.debug("[[" + internalCallId + "]] Body=" + msg);

                        MessageMessage mm = new MessageMessage(evt.getData());

                        if (msg.equals("callback")) {
                            CallSession cs = new CallSession();
                            logger.debug("[[" + internalCallId + "]] created call session : [[" + cs.internalCallId
                                    + "]]");

                            cs.offerPayloads.add(CallSession.PAYLOAD_PCMU);
                            cs.offerPayloads.add(CallSession.PAYLOAD_PCMA);

                            Session sess = SessionManager.findCreateSession(cs.jabberLocal.getDomain(),
                                    cs.jabberRemote);

                            sess.startCall(cs, evt.getData().getTo().getNode(),
                                    UriMappings.toSipId(evt.getData().getFrom()));
                        }

                        else if (msg.toLowerCase().startsWith("/dtmf:") || msg.toLowerCase().startsWith("/dial:")) {
                            logger.debug("[[" + internalCallId + "]] DIAL command detected");
                            CallSession cs = CallManager.getSession(evt.getData().getFrom(), evt.getData().getTo());
                            if (cs != null) {
                                logger.debug("[[" + internalCallId + "]] got call session : [[" + cs.internalCallId
                                        + "]]");
                                logger.debug("[[" + internalCallId + "]] Call found, sending dtmfs");
                                for (int i = "/dial:".length(); i < msg.length(); i++) {
                                    if (useDtmfInfo) {
                                        SipService.sendDTMFinfo(cs, msg.charAt(i), dtmfDuration);
                                    } else {
                                        cs.relay.sendSipDTMF(msg.charAt(i));

                                    }
                                }
                            }
                        }

                        else if (msg.equals("/echo") || msg.startsWith("/echo")) {
                            logger.debug("[[" + internalCallId + "]] Got an ECHO request");
                            Packet p = conn.getDataFactory().createPacketNode(new NSI("message", "jabber:server"),
                                    Packet.class);

                            p.setFrom(evt.getData().getTo());
                            p.setTo(evt.getData().getFrom());
                            p.setID(evt.getData().getID());
                            p.addElement("body");
                            String echo = msg.substring(msg.lastIndexOf('/') + 5);
                            p.getFirstElement("body").addText("Echo: " + echo);
                            p.setAttributeValue("type", "chat");
                            p.setAttributeValue("iconset", "round");
                            // initial experiment with chatstates
                            StreamElement chatstate = conn.getDataFactory()
                                    .createElementNode(new NSI("active", "http://jabber.org/protocol/chatstates"));
                            p.add(chatstate);
                            Session sess = SessionManager.findCreateSession(evt.getData().getTo().getDomain(),
                                    evt.getData().getFrom());
                            sess.sendPacket(p);
                        } else if (msg.equals("/me") || msg.startsWith("/me")) {

                            String slashme = msg.substring(msg.lastIndexOf('/') + 3);
                            StreamElement newbody = conn.getDataFactory()
                                    .createElementNode(new NSI("body", slashme));
                            body = newbody;
                            logger.debug("[[" + internalCallId + "]] Got a SLASHME request:");
                            String domain = host;

                            SipSubscription sub = SipSubscriptionManager.getWatcher(mm.from, mm.to);
                            if (sub != null) {
                                domain = ((SipURI) sub.remoteParty.getURI()).getHost();
                            }
                            SipService.sendMessageMessage(mm, domain);

                        }

                        else {
                            /* Forward the message to SIP side */
                            /* For coherence, we try to use the domain he has used in his subscription */
                            String domain = host;

                            SipSubscription sub = SipSubscriptionManager.getWatcher(mm.from, mm.to);
                            if (sub != null) {
                                domain = ((SipURI) sub.remoteParty.getURI()).getHost();
                            }
                            SipService.sendMessageMessage(mm, domain);
                        }
                    }
                }

            }

            else if (evt.getData().getQualifiedName().equals(":presence")) {
                logger.debug("[[" + internalCallId + "]] Got presence stanza");
                String type = evt.getData().getAttributeValue("type");

                if (type == null || type.equals("unavailable")) {
                    logger.debug(
                            "[[" + internalCallId + "]] Got a presence message from " + evt.getData().getFrom());

                    StreamElement caps = evt.getData()
                            .getFirstElement(new NSI("c", "http://jabber.org/protocol/caps"));

                    if (caps != null && caps.getAttributeValue("ext") != null) {
                        logger.debug("[[" + internalCallId + "]] Caps found");
                        if (caps.getAttributeValue("ext").contains("voice-v1")) {
                            logger.debug("[[" + internalCallId + "]] Voice support detected, taking note");
                            UriMappings.addVoiceResource(evt.getData().getFrom());
                        }
                        /*
                        if (caps.getAttributeValue("ext").contains("video-v1"))
                        {
                           logger.debug("[[" + internalCallId + "]] Video support detected, not taking note");
                        }
                            
                        if (caps.getAttributeValue("ext").contains("vainvite-v1"))
                        {
                           logger.debug("[[" + internalCallId + "]] 'vainvite' undocumented feature. Ignored for now. [" + evt.getData().getFrom() + "]" );
                        }
                        */
                    }
                    Presence pres = new Presence(evt.getData());
                    String from = UriMappings.toSipId(evt.getData().getFrom());
                    String to = evt.getData().getTo().getNode();
                    SipSubscription sub = SipSubscriptionManager.getWatcher(from, to);
                    if (sub != null) {
                        logger.debug("[[" + internalCallId + "]] Found matching subscription! Sending NOTIFY");
                        sub.sendNotify(false, pres);
                    }
                } else {
                    if (type.equals("subscribe")) {
                        logger.debug("[[" + internalCallId + "]] New subscription received from "
                                + evt.getData().getFrom());
                        String from = UriMappings.toSipId(evt.getData().getFrom());
                        String to = evt.getData().getTo().getNode();

                        if (!to.equals(fakeId)) {
                            SipSubscription sub = SipSubscriptionManager.getSubscription(from, to);
                            if (sub == null) {
                                logger.debug("[[" + internalCallId + "]] No existing subscription, sending one");
                                sub = new SipSubscription(from, to);
                                SipSubscriptionManager.addSubscriber(from, sub);
                                sub.sendSubscribe(false);
                            } else if (sub.remoteTag != null) {
                                logger.debug("[[" + internalCallId + "]] Subscription exists, sending refresh");
                                sub.sendSubscribe(false);
                            }
                        } else {
                            logger.debug("[[" + internalCallId + "]] Subscription to " + fakeId
                                    + ", sending dummy accept");
                            Session sess = SessionManager.findCreateSession(host, evt.getData().getFrom());
                            sess.sendSubscribeRequest(new JID(fakeId + "@" + host), evt.getData().getFrom(),
                                    "subscribed");
                        }
                    } else if (type.equals("unsubscribe")) {
                        logger.debug("[[" + internalCallId + "]] Unsubscribe  request");

                        String from = UriMappings.toSipId(evt.getData().getFrom());
                        String to = evt.getData().getTo().getNode();
                        if (!to.equals(fakeId)) {
                            SipSubscription sub = SipSubscriptionManager.removeSubscription(from, to);
                            if (sub != null) {
                                logger.debug("[[" + internalCallId + "]] Removing subscription");
                                sub.sendSubscribe(true);
                            }

                            sub = SipSubscriptionManager.getWatcher(from, to);
                            if (sub != null) {
                                logger.debug("[[" + internalCallId + "]] Removing watcher");
                                SipSubscriptionManager.removeWatcher(from, sub);
                                sub.sendNotify(true, null);
                            }
                        }

                    } else if (type.equals("subscribed")) {
                        logger.debug(
                                "[[" + internalCallId + "]] Jabber client accepted subscription, sending notify");
                        String from = UriMappings.toSipId(evt.getData().getFrom());
                        String to = evt.getData().getTo().getNode();
                        if (!to.equals(fakeId)) {
                            SipSubscription sub = SipSubscriptionManager.getWatcher(from, to);
                            sub.sendNotify(false, null);
                        }
                    } else if (type.equals("probe")) {
                        String from = UriMappings.toSipId(evt.getData().getFrom());
                        String to = evt.getData().getTo().getNode();
                        logger.debug("[[" + internalCallId + "]] Probe received from " + from + " to " + to);

                        if (to.equals(fakeId) || subscribeEmu) {
                            // subscribe emulation is forced or fake, send a dummy presence stanza + subscribed status
                            Session sess = SessionManager.findCreateSession(host, evt.getData().getFrom());

                            if (subscribeEmu) {
                                logger.debug("[[" + internalCallId + "]] Probe from " + from
                                        + ", sending emulated presence from " + to);
                                sess.sendSubscribeRequest(new JID(to + "@" + host), evt.getData().getFrom(),
                                        "subscribed");
                                sess.sendPresence(Presence.buildOnlinePresence(to, from, host));
                            } else {
                                logger.debug("[[" + internalCallId + "]] Probe to " + fakeId
                                        + ", sending dummy presence to " + from);
                                sess.sendSubscribeRequest(new JID(fakeId + "@" + host), evt.getData().getFrom(),
                                        "subscribed");
                                sess.sendPresence(Presence.buildOnlinePresence(fakeId, from, host));
                            }

                        } else if (!to.equals(fakeId)) {
                            SipSubscription sub = SipSubscriptionManager.getSubscription(from, to);
                            if (sub != null) {
                                logger.debug(
                                        "[[" + internalCallId + "]] Found a subscription, sending re-subscribe");
                                sub.sendSubscribe(false);
                            } else {
                                logger.debug("[[" + internalCallId
                                        + "]] No Subscription for this person, sending 0 length one");
                                sub = new SipSubscription(from, to);
                                SipSubscriptionManager.addSubscriber(from, sub);
                                sub.sendSubscribe(false);
                            }

                        }

                    }
                }
            } else if (evt.getData().getQualifiedName().equals(":iq")) {
                Packet packet = evt.getData();
                logger.debug("[[" + internalCallId + "]] got disco packet");
                if (packet.getAttributeValue("type").equals("get") && packet.getFirstElement().getNSI()
                        .equals(new NSI("query", "http://jabber.org/protocol/disco#info"))) {
                    logger.debug("[[" + internalCallId + "]] Got a feature query");
                    Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

                    p.setFrom(packet.getTo());
                    p.setTo(packet.getFrom());
                    p.setID(packet.getID());
                    p.setAttributeValue("type", "result");

                    StreamElement query = conn.getDataFactory()
                            .createElementNode(new NSI("query", "http://jabber.org/protocol/disco#info"));
                    query.setAttributeValue("node", clientName + "#" + clientVersion);

                    // Google-Centric Feature Set

                    if (featPMUC) {
                        query.addElement("feature").setAttributeValue("var",
                                "http://www.google.com/xmpp/protocol/pmuc/v1");
                    }
                    if (featSMS) {
                        query.addElement("feature").setAttributeValue("var",
                                "http://www.google.com/xmpp/protocol/pmuc/v1");
                    }

                    // Voice MUST be enabled
                    query.addElement("feature").setAttributeValue("var",
                            "http://www.google.com/xmpp/protocol/voice/v1");

                    if (featVID) {
                        query.addElement("feature").setAttributeValue("var",
                                "http://www.google.com/xmpp/protocol/video/v1");
                        query.addElement("feature").setAttributeValue("var",
                                "http://www.google.com/xmpp/protocol/camera/v1");
                    }

                    // General XEP Set

                    if (featPING) {
                        // xep-0199
                        query.addElement("feature").setAttributeValue("var", "urn:xmpp:ping");
                    }

                    if (featPMUC && featPMUC_UNI) {
                        // xep-0199
                        query.addElement("feature").setAttributeValue("var",
                                "http://jabber.org/protocol/muc#unique");
                    }

                    p.add(query);

                    Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(), packet.getFrom());
                    sess.sendPacket(p);
                }

                // XEP-0307 (used in G+ hangouts)
                else if (packet.getAttributeValue("type").equals("get") && packet.getFirstElement().getNSI()
                        .equals(new NSI("unique", "http://jabber.org/protocol/muc#unique"))) {

                    Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(), packet.getFrom());

                    if (featPMUC && featPMUC_UNI) {
                        // Result with UUID
                        sess.unIQ(packet);
                    } else {
                        // Status Unavailable - not required by XEP specification
                        // sess.errorIQ(packet, "unavailable");
                    }

                }

                // XEP-0199
                else if (packet.getAttributeValue("type").equals("get")
                        && packet.getFirstElement().getNSI().equals(new NSI("ping", "urn:xmpp:ping"))) {

                    Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(), packet.getFrom());

                    if (featPING) {
                        // Result Pong
                        sess.ackIQ(packet);
                    } else {
                        // XMPP Ping Not Supported
                        sess.cancelIQ(packet);
                    }

                }

                else if (packet.getAttributeValue("type").equals("error")) {
                    logger.debug("[[" + internalCallId + "]] Got error stanza");

                    StreamElement error = packet.getFirstElement("error");
                    if (error != null) {
                        logger.debug("[[" + internalCallId + "]] Error code: " + error.getAttributeValue("code")
                                + " type: " + error.getAttributeValue("type"));
                        if (error.getAttributeValue("type") == "cancel") {
                            logger.debug("[[" + internalCallId + "]] Sending cancel...");
                            String sessionId = packet
                                    .getFirstElement(new NSI("session", "http://www.google.com/session")).getID();
                            CallSession cs = CallManager.getSession(sessionId);
                            if (cs != null) {
                                SipService.sendReject(cs);
                                logger.debug("[[" + internalCallId + "]] Removing session... ");
                                CallManager.removeSession(cs);
                            }
                        } else {
                            logger.debug("[[" + internalCallId + "]] Proceeding... ");
                        }
                    }
                }

                else if (packet.getAttributeValue("type").equals("set")
                        && packet.getFirstElement(new NSI("jingle", "urn:xmpp:jingle:1")) != null && clientJingle) {
                    StreamElement session = packet.getFirstElement(new NSI("jingle", "urn:xmpp:jingle:1"));
                    String action = session.getAttributeValue("action");

                    if (action.equals("session-initiate")) {
                        handleInitate(packet, true);
                    } else if (action.equals("transport-info")) {
                        logger.debug("[[" + internalCallId + "]] Got candidate");
                        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(),
                                packet.getFrom());
                        sess.ackIQ(packet);
                        String sessionId = session.getAttributeValue("sid");
                        CallSession cs = CallManager.getSession(sessionId);
                        for (Object objContent : session.listElements("content")) {
                            StreamElement content = (StreamElement) objContent;
                            StreamElement origTransport = content.getFirstElement("transport");
                            handleTransportList(sess, origTransport, cs);
                        }

                    } else if (action.equals("session-accept")) {
                        logger.debug("[[" + internalCallId + "]] Got session accept");
                        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(),
                                packet.getFrom());
                        sess.ackIQ(packet);
                        String sessionId = session.getAttributeValue("sid");
                        CallSession cs = CallManager.getSession(sessionId);
                        if (cs != null) {
                            logger.debug(
                                    "[[" + internalCallId + "]] got call session : [[" + cs.internalCallId + "]]");
                            logger.debug("[[" + internalCallId + "]] Call found sending 200 OK");
                            cs.parseAccept(packet, true);
                            SipService.acceptCall(cs);
                        }
                    } else if (action.equals("session-terminate")) {
                        logger.debug("[[" + internalCallId + "]] Got session terminate");
                        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(),
                                packet.getFrom());
                        sess.ackIQ(packet);
                        String sessionId = session.getAttributeValue("sid");
                        CallSession cs = CallManager.getSession(sessionId);
                        if (cs != null) {
                            logger.debug(
                                    "[[" + internalCallId + "]] got call session : [[" + cs.internalCallId + "]]");

                            if (cs.sipDialog.getState() == null || cs.sipDialog.getState() != DialogState.EARLY) {
                                logger.debug("[[" + internalCallId + "]] Call found sending BYE");
                            } else {
                                logger.debug(
                                        "[[" + internalCallId + "]] Call found in Early state, sending CANCEL");
                            }

                            SipService.sendBye(cs);

                            CallManager.removeSession(cs);
                        }
                    } else if (action.equals("session-info")) {
                        logger.debug("[[" + internalCallId + "]] Got session-info request");
                        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(),
                                packet.getFrom());
                        sess.ackIQ(packet);
                        String sessionId = session.getAttributeValue("sid");
                        CallSession cs = CallManager.getSession(sessionId);
                        for (Object objContent : session.listElements("content")) {
                            StreamElement content = (StreamElement) objContent;
                            StreamElement origTransport = content.getFirstElement("transport");
                            handleTransportList(sess, origTransport, cs);
                        }

                    } else {
                        logger.debug("[[" + internalCallId + "]] Unhandled action: " + action);
                    }

                }

                // gingle  call
                else if (packet.getAttributeValue("type").equals("set")
                        && packet.getFirstElement(new NSI("session", "http://www.google.com/session")) != null) {
                    if (packet.getFirstElement(new NSI("session", "http://www.google.com/session"))
                            .getAttributeValue("type").equals("initiate")) {
                        handleInitate(packet, false);
                    } else if (packet.getFirstElement(new NSI("session", "http://www.google.com/session"))
                            .getAttributeValue("type").equals("transport-info")) {
                        logger.debug("[[" + internalCallId + "]] Got transport info");
                        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(),
                                packet.getFrom());
                        sess.ackIQ(packet);
                        StreamElement session = packet
                                .getFirstElement(new NSI("session", "http://www.google.com/session"));
                        String sessionId = session.getID();
                        CallSession cs = CallManager.getSession(sessionId);

                        StreamElement origTransport = packet
                                .getFirstElement(new NSI("session", "http://www.google.com/session"))
                                .getFirstElement("transport");
                        handleTransportList(sess, origTransport, cs);

                    } else if (packet.getFirstElement(new NSI("session", "http://www.google.com/session"))
                            .getAttributeValue("type").equals("candidates")) {
                        logger.debug("[[" + internalCallId + "]] Got candidate");
                        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(),
                                packet.getFrom());
                        sess.ackIQ(packet);

                        StreamElement session = packet
                                .getFirstElement(new NSI("session", "http://www.google.com/session"));
                        String sessionId = session.getID();
                        CallSession cs = CallManager.getSession(sessionId);
                        handleTransportList(sess, session, cs);
                    } else if (packet.getFirstElement(new NSI("session", "http://www.google.com/session"))
                            .getAttributeValue("type").equals("transport-accept")) {
                        logger.debug("[[" + internalCallId + "]] Got transport accept");
                        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(),
                                packet.getFrom());
                        sess.ackIQ(packet);
                    } else if (packet.getFirstElement(new NSI("session", "http://www.google.com/session"))
                            .getAttributeValue("type").equals("accept")) {
                        logger.debug("[[" + internalCallId + "]] Got transport accept");
                        Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(),
                                packet.getFrom());
                        sess.ackIQ(packet);
                        CallSession cs = CallManager.getSession(packet
                                .getFirstElement(new NSI("session", "http://www.google.com/session")).getID());
                        if (cs != null) {
                            logger.debug(
                                    "[[" + internalCallId + "]] got call session : [[" + cs.internalCallId + "]]");
                            logger.debug("[[" + internalCallId + "]] Call found sending 200 OK");
                            cs.parseAccept(packet, false);
                            SipService.acceptCall(cs);
                        }
                    } else if (packet.getFirstElement(new NSI("session", "http://www.google.com/session"))
                            .getAttributeValue("type").equals("terminate")) {
                        logger.debug("[[" + internalCallId + "]] Got a terminate");
                        String sessionId = packet
                                .getFirstElement(new NSI("session", "http://www.google.com/session")).getID();
                        CallSession cs = CallManager.getSession(sessionId);
                        if (cs != null) {
                            logger.debug(
                                    "[[" + internalCallId + "]] got call session : [[" + cs.internalCallId + "]]");
                            if (cs.sipDialog.getState() != DialogState.EARLY) {
                                logger.debug("[[" + internalCallId + "]] Call found sending BYE");
                                SipService.sendBye(cs);
                            } else {
                                logger.debug(
                                        "[[" + internalCallId + "]] Call found in Early state, sending CANCEL");
                                SipService.sendReject(cs);
                            }
                            CallManager.removeSession(cs);
                        }
                    }

                    else if (packet.getFirstElement(new NSI("session", "http://www.google.com/session"))
                            .getAttributeValue("type").equals("reject")) {
                        logger.debug("[[" + internalCallId + "]] Got a reject");
                        String sessionId = packet
                                .getFirstElement(new NSI("session", "http://www.google.com/session")).getID();
                        CallSession cs = CallManager.getSession(sessionId);
                        if (cs != null) {
                            logger.debug(
                                    "[[" + internalCallId + "]] got call session : [[" + cs.internalCallId + "]]");
                            logger.debug("[[" + internalCallId + "]] found call session, forwarding reject");
                            SipService.sendReject(cs);
                            CallManager.removeSession(cs);
                        }

                    }
                }

                else if (packet.getAttributeValue("type").equals("set")
                        && packet.getFirstElement(new NSI("otr", "http://jabber.org/protocol/archive")) != null) {
                    logger.debug("[[" + internalCallId
                            + "]] Got a Jabber Archive/Record status change, forwarding.... ");

                    String otr = "";
                    Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);
                    p.setFrom(packet.getTo());
                    p.setTo(packet.getFrom());
                    p.setID(packet.getID());

                    MessageMessage mm = new MessageMessage(evt.getData());

                    if (packet.getFirstElement(new NSI("otr", "http://jabber.org/protocol/archive"))
                            .getFirstElement("record").getAttributeValue("otr").equals("true")) {
                        logger.info("[[" + internalCallId + "]] chat session is OFF the records ");
                        otr = "Chat session is OFF RECORDS";
                    } else if (packet.getFirstElement(new NSI("otr", "http://jabber.org/protocol/archive"))
                            .getFirstElement("record").getAttributeValue("otr").equals("false")) {
                        logger.info("[[" + internalCallId + "]] chat session is RECORDED ");
                        otr = "Chat session is RECORDED";
                    }

                    mm.body = "[" + otr + "]";
                    String domain = host;

                    SipSubscription sub = SipSubscriptionManager.getWatcher(mm.from, mm.to);
                    if (sub != null) {
                        domain = ((SipURI) sub.remoteParty.getURI()).getHost();
                    }
                    SipService.sendMessageMessage(mm, domain);
                }

                else if (packet.getAttributeValue("type").equals("get")
                        && packet.getFirstElement().getNSI().equals(new NSI("vCard", "vcard-temp"))) {
                    logger.debug("[[" + internalCallId + "]] Got an ICON request!");
                    Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

                    p.setFrom(packet.getTo());
                    p.setTo(packet.getFrom());
                    p.setID(packet.getID());
                    p.setAttributeValue("type", "result");

                    StreamElement query = conn.getDataFactory().createElementNode(new NSI("vCard", "vcard-temp"));

                    StreamElement fullName = query.addElement("FN");

                    //if a telephone number  add a + to be pretty
                    if (packet.getTo().getNode().toString().matches("[0-9]+")) {
                        fullName.addText("+" + packet.getTo().getNode().toString());
                    } else {
                        fullName.addText(packet.getTo().getNode().toString());
                    }
                    StreamElement photo = query.addElement("PHOTO");
                    StreamElement type = photo.addElement("TYPE");
                    type.addText("image/jpeg");
                    StreamElement binval = photo.addElement("BINVAL");
                    byte[] encoded = Base64.encodeBase64Chunked(iconData);
                    binval.addText(new String(encoded));

                    p.add(query);

                    Session sess = SessionManager.findCreateSession(packet.getTo().getDomain(), packet.getFrom());

                    sess.sendPacket(p);
                }
            }
        } catch (Exception e) {
            logger.error("Exception in packetTransferred", e);
        }
    }

    private void ackIQ(Packet packet) throws StreamException {
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

        p.setFrom(packet.getTo());
        p.setTo(packet.getFrom());
        p.setID(packet.getID());
        p.setAttributeValue("type", "result");
        sendPacket(p);
    }

    private void cancelIQ(Packet packet) throws StreamException {
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

        p.setFrom(packet.getTo());
        p.setTo(packet.getFrom());
        p.setID(packet.getID());
        p.setAttributeValue("type", "error");
        StreamElement error = conn.getDataFactory().createElementNode(new NSI("error", "cancel"));
        error.addElement("service-unavailable").setAttributeValue("xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas");
        p.add(error);
        sendPacket(p);
    }

    // XEP-0307 Unique MUC 
    private void unIQ(Packet packet) throws StreamException {
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);
        p.setFrom(packet.getTo());
        p.setTo(packet.getFrom());
        p.setID(packet.getID());
        p.setAttributeValue("type", "result");
        p.addElement("unique").setAttributeValue("xmlns", "http://jabber.org/protocol/muc#unique");
        p.getFirstElement("unique").setAttributeValue("hangout-id", "Kelpie-hangout");
        String uniq = UUID.randomUUID().toString();
        p.getFirstElement("unique").addText(uniq);
        sendPacket(p);
    }

    // XEP-0167 Jingle Informational Messages (generic prototype helper)
    private void sessinfoIQ(Packet packet, String action) throws StreamException {
        // actions: active, hold, unhold, ringing
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);
        p.setFrom(packet.getTo());
        p.setTo(packet.getFrom());
        p.setID(packet.getID());
        p.setAttributeValue("type", "set");
        p.addElement("jingle").setAttributeValue("xmlns", "urn:xmpp:jingle:1");
        p.getFirstElement("jingle").setAttributeValue("action", "session-info");
        p.getFirstElement("jingle").setAttributeValue("initiator", packet.getFrom().toString());
        p.getFirstElement("jingle").addElement(action).setAttributeValue("xmlns",
                "urn:xmpp:jingle:apps:rtp:info:1");
        sendPacket(p);
    }

    // XEP-0167 Jingle Informational Messages (mute helper)
    private void muteIQ(Packet packet, String action, String creator, String resource) throws StreamException {
        // actions: mute, unmute resource: audio, video, both (both)
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);
        p.setFrom(packet.getTo());
        p.setTo(packet.getFrom());
        p.setID(packet.getID());
        p.setAttributeValue("type", "set");
        p.addElement("jingle").setAttributeValue("xmlns", "urn:xmpp:jingle:1");
        p.getFirstElement("jingle").setAttributeValue("action", "session-info");
        p.getFirstElement("jingle").setAttributeValue("initiator", packet.getFrom().toString());
        p.getFirstElement("jingle").addElement(action).setAttributeValue("xmlns",
                "urn:xmpp:jingle:apps:rtp:info:1");
        p.getFirstElement(action).setAttributeValue("creator", creator);
        if (resource != null && resource != "both") {
            p.getFirstElement(action).setAttributeValue("name", resource);
        }
        sendPacket(p);
    }

    // error helper
    private void errorIQ(Packet packet, String... type) throws StreamException {
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

        p.setFrom(packet.getTo());
        p.setTo(packet.getFrom());
        p.setID(packet.getID());
        p.setAttributeValue("type", "error");
        StreamElement error = conn.getDataFactory().createElementNode(new NSI("error", "cancel"));
        if (type != null) {
            for (int i = 0; i < type.length; i++) {
                if (type[i] == "limit") {
                    error.addElement("resource-constraint").setAttributeValue("xmlns",
                            "urn:ietf:params:xml:ns:xmpp-stanzas");
                    error.addElement("resource-limit-exceeded").setAttributeValue("xmlns", "urn:xmpperrors");
                } else if (type[i] == "unavailable") {
                    error.addElement("service-unavailable").setAttributeValue("xmlns",
                            "urn:ietf:params:xml:ns:xmpp-stanzas");
                } else if (type[i] == "forbidden") {
                    p.getFirstElement("error").setAttributeValue("type", "auth");
                    error.addElement("forbidden").setAttributeValue("xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas");
                }
            }
        }

        p.add(error);
        sendPacket(p);
    }

    public void acceptTransport(Packet origPacket) throws StreamException {
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

        logger.debug("[[" + internalCallId + "]] Accepting a transport");

        p.setFrom(origPacket.getTo());
        p.setTo(origPacket.getFrom());
        p.setID(Long.toString(++this.idNum));
        p.setAttributeValue("type", "set");

        StreamElement origSession = origPacket.getFirstElement();
        StreamElement session = p.addElement(new NSI("session", "http://www.google.com/session"));
        session.setAttributeValue("type", "transport-accept");
        session.setID(origSession.getID());
        session.setAttributeValue("initiator", origSession.getAttributeValue("initiator"));
        session.addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

        sendPacket(p);
    }

    public boolean sendSubscribeRequest(JID from, JID to, String type) {
        Packet p = conn.getDataFactory().createPacketNode(new NSI("presence", Utilities.SERVER_NAMESPACE),
                Packet.class);
        p.setFrom(from);
        p.setTo(to);
        p.setAttributeValue("type", type);

        // Add status to subscribed stanza, FS-Style
        if (type == "subscribed") {
            StreamElement status = p.addElement("status");
            status.addText(statusNoteOnline);
        }

        if (featNICK && type == "subscribe") {
            String nick = from.toString().split("@")[0];
            if (nick.contains("+")) {
                nick = nick.split("+")[0];
            }
            StreamElement nicktag = p.addElement(new NSI("nick", "http://jabber.org/protocol/nick"));
            nicktag.addText(nick);
        }

        try {
            sendPacket(p);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Stream Error sending Subscribe!", e);
            return false;
        }
        return true;
    }

    public boolean startCall(CallSession callSession, String from, String to) {
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", Utilities.SERVER_NAMESPACE), Packet.class);

        callSession.jabberLocal = new JID(from + "@" + host + "/" + fakeId);
        callSession.jabberRemote = new JID(
                UriMappings.toJID(to).toString() + "/" + UriMappings.getVoiceResource(UriMappings.toJID(to)));
        callSession.jabberInitiator = callSession.jabberLocal.toString();

        callSession.jabberSessionId = Integer.toString((int) (Math.random() * 1000000000));

        CallManager.addSession(callSession);

        p.setFrom(callSession.jabberLocal);
        p.setTo(callSession.jabberRemote);

        p.setID(Long.toString(++this.idNum));
        p.setAttributeValue("type", "set");

        // Jingle Init
        StreamElement jin = null;
        StreamElement jin_transport = null;
        StreamElement content = null;

        if (clientJingle) {
            jin = p.addElement(new NSI("jingle", "urn:xmpp:jingle:1"));

            jin.setAttributeValue("action", "session-initiate");
            jin.setAttributeValue("initiator", callSession.jabberInitiator);
            jin.setAttributeValue("sid", callSession.jabberSessionId);

            content = jin.addElement("content");
            content.setAttributeValue("name", "audio");
            content.setAttributeValue("creator", "initiator");

            jin_transport = content.addElement(new NSI("transport", "urn:xmpp:jingle:apps:rtp:1"));
        }

        // Gingle Init
        StreamElement session = p.addElement(new NSI("session", "http://www.google.com/session"));

        session.setAttributeValue("type", "initiate");
        session.setID(callSession.jabberSessionId);

        session.setAttributeValue("initiator", callSession.jabberInitiator);

        StreamElement description = null;
        StreamElement vdescription = null;
        StreamElement jin_description = null;
        StreamElement jin_vdescription = null;

        // Video + Audio call
        if (callSession.vRelay != null) {
            // Gingle video 
            vdescription = session.addElement(new NSI("description", "http://www.google.com/session/video"));
            // Gingle audio
            description = session.addElement(new NSI("description", "http://www.google.com/session/phone"));

            if (clientJingle) {
                // Jingle video 
                StreamElement content_vid = jin.addElement("content");
                content_vid.setAttributeValue("name", "video");
                content_vid.setAttributeValue("creator", "initiator");

                jin_vdescription = content_vid.addElement(new NSI("description", "urn:xmpp:jingle:apps:rtp:1"));
                jin_vdescription.setAttributeValue("media", "video");
                // jin_transport = content_vid.addElement(new NSI("transport", "http://www.google.com/transport/p2p"));
                content_vid.addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

                // Jingle audio
                jin_description = content.addElement(new NSI("description", "urn:xmpp:jingle:apps:rtp:1"));
                jin_description.setAttributeValue("media", "audio");
            }

            for (CallSession.VPayload payload : callSession.offerVPayloads) {
                // Gingle Video
                StreamElement payload_type = vdescription.addElement("payload-type");
                payload_type.setAttributeValue("id", Integer.toString(payload.id));
                payload_type.setAttributeValue("name", payload.name);
                payload_type.setAttributeValue("width", Integer.toString(payload.width));
                payload_type.setAttributeValue("height", Integer.toString(payload.height));
                payload_type.setAttributeValue("framerate", Integer.toString(payload.framerate));

                // Jingle Video
                if (clientJingle) {
                    StreamElement jin_payload_type = jin_vdescription.addElement("payload-type");

                    jin_payload_type.setAttributeValue("id", Integer.toString(payload.id));
                    jin_payload_type.setAttributeValue("name", payload.name);

                    StreamElement jin_payload_video = jin_payload_type.addElement("parameter");
                    jin_payload_video.setAttributeValue("name", "width");
                    jin_payload_video.setAttributeValue("value", Integer.toString(payload.width));

                    jin_payload_video = jin_payload_type.addElement("parameter");
                    jin_payload_video.setAttributeValue("name", "height");
                    jin_payload_video.setAttributeValue("value", Integer.toString(payload.height));
                    jin_payload_video = jin_payload_type.addElement("parameter");
                    jin_payload_video.setAttributeValue("name", "framerate");
                    jin_payload_video.setAttributeValue("value", Integer.toString(payload.framerate));
                }

            }
        }

        // Audio Call
        else {
            // Gingle
            description = session.addElement(new NSI("description", "http://www.google.com/session/phone"));

            // Jingle audio content
            if (clientJingle) {
                jin_description = content.addElement(new NSI("description", "urn:xmpp:jingle:apps:rtp:1"));
                jin_description.setAttributeValue("media", "audio");
            }
        }

        for (CallSession.Payload payload : callSession.offerPayloads) {
            // Gingle Audio
            StreamElement payload_type = description.addElement("payload-type",
                    "http://www.google.com/session/phone");

            payload_type.setAttributeValue("id", Integer.toString(payload.id));
            payload_type.setAttributeValue("clockrate", Integer.toString(payload.clockRate));
            payload_type.setAttributeValue("bitrate", Integer.toString(payload.bitRate));
            payload_type.setAttributeValue("name", payload.name);

            // Jingle Audio
            if (clientJingle) {
                StreamElement jin_payload_type = jin_description.addElement("payload-type");

                jin_payload_type.setAttributeValue("id", Integer.toString(payload.id));
                jin_payload_type.setAttributeValue("name", payload.name);
                jin_payload_type.setAttributeValue("clockrate", Integer.toString(payload.clockRate));
                if (Integer.toString(payload.clockRate) != null) {
                    StreamElement jin_payload_bitrate = jin_payload_type.addElement("parameter");
                    jin_payload_bitrate.setAttributeValue("name", "bitrate");
                    jin_payload_bitrate.setAttributeValue("value", Integer.toString(payload.bitRate));
                }
            }

        }

        /*StreamElement transport = */session
                .addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

        try {
            sendPacket(p);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Stream error in startCall", e);
            return false;
        }

        if (callSession.vRelay != null) {
            callSession.sentTransport = true;
            callSession.sentVTransport = true;
            sendTransportCandidates(callSession, StreamType.RTCP);
            sendTransportCandidates(callSession, StreamType.RTP);
            sendTransportCandidates(callSession, StreamType.VRTCP);
            sendTransportCandidates(callSession, StreamType.VRTP);
        } else {
            callSession.sentTransport = true;
            sendTransportInfo(callSession);
        }

        return true;
    }

    private boolean sendTransportInfo(CallSession callSession) {
        Packet p;
        StreamElement session;
        StreamElement transport;
        p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

        p.setFrom(callSession.jabberLocal);
        p.setTo(callSession.jabberRemote);
        p.setID(Long.toString(++this.idNum));
        p.setAttributeValue("type", "set");

        Random r = new Random();
        byte[] bytes = new byte[4];
        r.nextBytes(bytes);

        callSession.candidateUser = String.format("%02x%02x%02x%02x", bytes[0], bytes[1], bytes[2], bytes[3]);

        // Jingle Candidate Info
        if (clientJingle) {
            StreamElement jin = p.addElement(new NSI("jingle", "urn:xmpp:jingle:1"));
            jin.setAttributeValue("action", "transport-info");
            jin.setAttributeValue("initiator", callSession.jabberInitiator);
            jin.setAttributeValue("sid", callSession.jabberSessionId);

            StreamElement content = jin.addElement("content");
            content.setAttributeValue("name", "audio");
            content.setAttributeValue("creator", "initiator");

            StreamElement jin_transport = content
                    .addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

            StreamElement jin_candidate = jin_transport.addElement("candidate");
            jin_candidate.setAttributeValue("name", "rtp");
            jin_candidate.setAttributeValue("address", SipService.getLocalIP());
            jin_candidate.setAttributeValue("port", Integer.toString(callSession.relay.getJabberPort()));
            jin_candidate.setAttributeValue("preference", "1");
            jin_candidate.setAttributeValue("username", callSession.candidateUser);
            jin_candidate.setAttributeValue("password", callSession.candidateUser);
            jin_candidate.setAttributeValue("protocol", "udp");

            jin_candidate.setAttributeValue("generation", "0");
            jin_candidate.setAttributeValue("type", "local");
            jin_candidate.setAttributeValue("network", "0");
        }

        // Gingle Candidate Info
        session = p.addElement(new NSI("session", "http://www.google.com/session"));
        session.setAttributeValue("type", "transport-info");
        session.setID(callSession.jabberSessionId);
        session.setAttributeValue("initiator", callSession.jabberInitiator);

        transport = session.addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

        StreamElement candidate = transport.addElement("candidate");
        candidate.setAttributeValue("name", "rtp");
        candidate.setAttributeValue("address", SipService.getLocalIP());
        candidate.setAttributeValue("port", Integer.toString(callSession.relay.getJabberPort()));
        candidate.setAttributeValue("preference", "1");
        candidate.setAttributeValue("username", callSession.candidateUser);
        candidate.setAttributeValue("password", callSession.candidateUser);
        candidate.setAttributeValue("protocol", "udp");

        candidate.setAttributeValue("generation", "0");
        candidate.setAttributeValue("type", "local");
        candidate.setAttributeValue("network", "0");

        try {
            sendPacket(p);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Error while sending TransportInfo", e);
            return false;
        }
        return true;
    }

    private boolean sendTransportCandidates(CallSession callSession, StreamType type) {
        Packet p;
        StreamElement session;

        Random r = new Random();
        byte[] bytes = new byte[4];
        r.nextBytes(bytes);

        if (type == StreamType.RTP || type == StreamType.RTCP) {
            if (callSession.candidateUser == null) {
                callSession.candidateUser = String.format("%02x%02x%02x%02x", bytes[0], bytes[1], bytes[2],
                        bytes[3]);
            }

            p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

            p.setFrom(callSession.jabberLocal);
            p.setTo(callSession.jabberRemote);
            p.setID(Long.toString(++this.idNum));
            p.setAttributeValue("type", "set");

            // Jingle Candidate Transport
            if (clientJingle) {
                StreamElement jin = p.addElement(new NSI("jingle", "urn:xmpp:jingle:1"));
                jin.setAttributeValue("action", "transport-info");
                jin.setAttributeValue("initiator", callSession.jabberInitiator);
                jin.setAttributeValue("sid", callSession.jabberSessionId);

                StreamElement content = jin.addElement("content");
                content.setAttributeValue("name", "audio");
                content.setAttributeValue("creator", "initiator");

                StreamElement jin_transport = content
                        .addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

                StreamElement jin_candidate = jin_transport.addElement("candidate");

                if (type == StreamType.RTP) {
                    jin_candidate.setAttributeValue("name", "rtp");
                    jin_candidate.setAttributeValue("address", SipService.getLocalIP());
                    jin_candidate.setAttributeValue("port", Integer.toString(callSession.relay.getJabberPort()));
                } else {
                    jin_candidate.setAttributeValue("name", "rtcp");
                    jin_candidate.setAttributeValue("address", SipService.getLocalIP());
                    jin_candidate.setAttributeValue("port",
                            Integer.toString(callSession.relay.getJabberRtcpPort()));
                }
                jin_candidate.setAttributeValue("preference", "1");
                jin_candidate.setAttributeValue("username", callSession.candidateUser);
                jin_candidate.setAttributeValue("password", callSession.candidateUser);
                jin_candidate.setAttributeValue("protocol", "udp");

                jin_candidate.setAttributeValue("generation", "0");
                jin_candidate.setAttributeValue("type", "local");
                jin_candidate.setAttributeValue("network", "0");
            }

            // Gingle Candidate Transport
            session = p.addElement(new NSI("session", "http://www.google.com/session"));
            session.setAttributeValue("type", "candidates");
            session.setID(callSession.jabberSessionId);
            session.setAttributeValue("initiator", callSession.jabberInitiator);

            StreamElement candidate = session.addElement("candidate");
            if (type == StreamType.RTP) {
                candidate.setAttributeValue("name", "rtp");
                candidate.setAttributeValue("address", SipService.getLocalIP());
                candidate.setAttributeValue("port", Integer.toString(callSession.relay.getJabberPort()));
            } else {
                candidate.setAttributeValue("name", "rtcp");
                candidate.setAttributeValue("address", SipService.getLocalIP());
                candidate.setAttributeValue("port", Integer.toString(callSession.relay.getJabberRtcpPort()));
            }
            candidate.setAttributeValue("preference", "1");
            candidate.setAttributeValue("username", callSession.candidateUser);
            candidate.setAttributeValue("password", callSession.candidateUser);
            candidate.setAttributeValue("protocol", "udp");

            candidate.setAttributeValue("generation", "0");
            candidate.setAttributeValue("type", "local");
            candidate.setAttributeValue("network", "0");

            try {
                sendPacket(p);
            } catch (StreamException e) {
                logger.error("[[" + internalCallId + "]] Error while sending audio TransportCandidates", e);
                return false;
            }
        } else if (type == StreamType.VRTP || type == StreamType.VRTCP) {
            if (callSession.candidateVUser == null) {
                callSession.candidateVUser = String.format("%02x%02x%02x%02x", bytes[0], bytes[1], bytes[2],
                        bytes[3]);
            }

            p = conn.getDataFactory().createPacketNode(new NSI("iq", "jabber:server"), Packet.class);

            p.setFrom(callSession.jabberLocal);
            p.setTo(callSession.jabberRemote);
            p.setID(Long.toString(++this.idNum));
            p.setAttributeValue("type", "set");

            // Jingle Candidate Transport
            if (clientJingle) {
                StreamElement jin = p.addElement(new NSI("jingle", "urn:xmpp:jingle:1"));
                jin.setAttributeValue("action", "transport-info");
                jin.setAttributeValue("initiator", callSession.jabberInitiator);
                jin.setAttributeValue("sid", callSession.jabberSessionId);

                StreamElement content = jin.addElement("content");
                content.setAttributeValue("name", "video");
                content.setAttributeValue("creator", "initiator");

                StreamElement jin_transport = content
                        .addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

                StreamElement jin_candidate = jin_transport.addElement("candidate");
                if (type == StreamType.VRTP) {
                    jin_candidate.setAttributeValue("name", "video_rtp");
                    jin_candidate.setAttributeValue("address", SipService.getLocalIP());
                    jin_candidate.setAttributeValue("port", Integer.toString(callSession.vRelay.getJabberPort()));
                } else {
                    jin_candidate.setAttributeValue("name", "video_rtcp");
                    jin_candidate.setAttributeValue("address", SipService.getLocalIP());
                    jin_candidate.setAttributeValue("port",
                            Integer.toString(callSession.vRelay.getJabberRtcpPort()));
                }
                jin_candidate.setAttributeValue("preference", "1");
                jin_candidate.setAttributeValue("username", callSession.candidateVUser);
                jin_candidate.setAttributeValue("password", callSession.candidateVUser);
                jin_candidate.setAttributeValue("protocol", "udp");

                jin_candidate.setAttributeValue("generation", "0");
                jin_candidate.setAttributeValue("type", "local");
                jin_candidate.setAttributeValue("network", "0");
            }

            // Gingle 
            session = p.addElement(new NSI("session", "http://www.google.com/session"));
            session.setAttributeValue("type", "candidates");
            session.setID(callSession.jabberSessionId);
            session.setAttributeValue("initiator", callSession.jabberInitiator);

            StreamElement candidate = session.addElement("candidate");
            if (type == StreamType.VRTP) {
                candidate.setAttributeValue("name", "video_rtp");
                candidate.setAttributeValue("address", SipService.getLocalIP());
                candidate.setAttributeValue("port", Integer.toString(callSession.vRelay.getJabberPort()));
            } else {
                candidate.setAttributeValue("name", "video_rtcp");
                candidate.setAttributeValue("address", SipService.getLocalIP());
                candidate.setAttributeValue("port", Integer.toString(callSession.vRelay.getJabberRtcpPort()));
            }
            candidate.setAttributeValue("preference", "1");
            candidate.setAttributeValue("username", callSession.candidateVUser);
            candidate.setAttributeValue("password", callSession.candidateVUser);
            candidate.setAttributeValue("protocol", "udp");

            candidate.setAttributeValue("generation", "0");
            candidate.setAttributeValue("type", "local");
            candidate.setAttributeValue("network", "0");

            try {
                sendPacket(p);
            } catch (StreamException e) {
                logger.error("[[" + internalCallId + "]] Error while sending video TransportCandidates", e);
                return false;
            }
        }
        return true;
    }

    public boolean sendAccept(CallSession callSession) {
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", Utilities.SERVER_NAMESPACE), Packet.class);

        p.setFrom(callSession.jabberLocal);
        p.setTo(callSession.jabberRemote);

        p.setID(Long.toString(++this.idNum));
        p.setAttributeValue("type", "set");

        // Jingle
        StreamElement jin = null;
        StreamElement content = null;
        StreamElement jin_transport = null;

        if (clientJingle) {
            jin = p.addElement(new NSI("jingle", "urn:xmpp:jingle:1"));
            jin.setAttributeValue("action", "session-accept");
            jin.setAttributeValue("initiator", callSession.jabberInitiator);
            jin.setAttributeValue("sid", callSession.jabberSessionId);

            content = jin.addElement("content");
            content.setAttributeValue("name", "audio");
            content.setAttributeValue("creator", "initiator");
            jin_transport = content.addElement(new NSI("transport", "http://www.google.com/transport/p2p"));
        }

        // Gingle
        StreamElement session = p.addElement(new NSI("session", "http://www.google.com/session"));

        session.setAttributeValue("type", "accept");
        session.setID(callSession.jabberSessionId);

        session.setAttributeValue("initiator", callSession.jabberInitiator);

        StreamElement description = null;
        StreamElement vdescription = null;
        StreamElement jin_description = null;
        StreamElement jin_vdescription = null;

        if (callSession.vRelay != null) {

            // Gingle Video
            vdescription = session.addElement(new NSI("description", "http://www.google.com/session/video"));

            // Gingle Audio
            description = session.addElement(new NSI("description", "http://www.google.com/session/phone"));

            if (clientJingle) {
                // Jingle Video
                StreamElement content_vid = jin.addElement("content");
                content_vid.setAttributeValue("name", "video");
                content_vid.setAttributeValue("creator", "initiator");

                jin_vdescription = content_vid.addElement(new NSI("description", "urn:xmpp:jingle:apps:rtp:1"));
                jin_vdescription.setAttributeValue("media", "video");
                jin_transport = content_vid.addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

                // Jingle Audio
                jin_description = content.addElement(new NSI("description", "urn:xmpp:jingle:apps:rtp:1"));
                jin_description.setAttributeValue("media", "audio");
            }

            for (CallSession.VPayload payload : callSession.answerVPayloads) {
                // Gingle Video Payloads
                StreamElement payload_type = vdescription.addElement("payload-type");

                payload_type.setAttributeValue("id", Integer.toString(payload.id));
                payload_type.setAttributeValue("name", payload.name);

                payload_type.setAttributeValue("width", Integer.toString(payload.width));
                payload_type.setAttributeValue("height", Integer.toString(payload.height));
                payload_type.setAttributeValue("framerate", Integer.toString(payload.framerate));

                payload_type.setAttributeValue("clockrate", Integer.toString(payload.clockRate));

                // Jingle Video Payloads
                if (clientJingle) {
                    StreamElement jin_payload_type = jin_vdescription.addElement("payload-type");

                    jin_payload_type.setAttributeValue("id", Integer.toString(payload.id));
                    jin_payload_type.setAttributeValue("name", payload.name);
                    StreamElement jin_payload_video = jin_payload_type.addElement("parameter");
                    jin_payload_video.setAttributeValue("name", "width");
                    jin_payload_video.setAttributeValue("value", Integer.toString(payload.width));

                    jin_payload_video = jin_payload_type.addElement("parameter");
                    jin_payload_video.setAttributeValue("name", "height");
                    jin_payload_video.setAttributeValue("value", Integer.toString(payload.height));
                    jin_payload_video = jin_payload_type.addElement("parameter");
                    jin_payload_video.setAttributeValue("name", "framerate");
                    jin_payload_video.setAttributeValue("value", Integer.toString(payload.framerate));
                }

            }
        } else {
            // Gingle audio
            description = session.addElement(new NSI("description", "http://www.google.com/session/phone"));

            // Jingle audio
            if (clientJingle) {
                jin_description = content.addElement(new NSI("description", "urn:xmpp:jingle:apps:rtp:1"));
                jin_description.setAttributeValue("media", "audio");
            }
        }

        for (CallSession.Payload payload : callSession.answerPayloads) {
            // Gingle audio payloads
            StreamElement payload_type = description.addElement("payload-type",
                    "http://www.google.com/session/phone");

            payload_type.setAttributeValue("id", Integer.toString(payload.id));
            payload_type.setAttributeValue("clockrate", Integer.toString(payload.clockRate));
            payload_type.setAttributeValue("bitrate", Integer.toString(payload.bitRate));
            payload_type.setAttributeValue("name", payload.name);

            // Jingle audio payloads
            if (clientJingle) {
                StreamElement jin_payload_type = jin_description.addElement("payload-type");

                jin_payload_type.setAttributeValue("id", Integer.toString(payload.id));
                jin_payload_type.setAttributeValue("name", payload.name);
                jin_payload_type.setAttributeValue("clockrate", Integer.toString(payload.clockRate));
                if (Integer.toString(payload.clockRate) != null) {
                    StreamElement jin_payload_bitrate = jin_payload_type.addElement("parameter");
                    jin_payload_bitrate.setAttributeValue("name", "bitrate");
                    jin_payload_bitrate.setAttributeValue("value", Integer.toString(payload.bitRate));
                }
            }
        }

        /*StreamElement transport = */session
                .addElement(new NSI("transport", "http://www.google.com/transport/p2p"));

        try {
            sendPacket(p);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Error sending accept", e);
            return false;
        }
        return true;
    }

    public boolean sendBye(CallSession callSession) {
        CallManager.removeSession(callSession);
        Packet p = conn.getDataFactory().createPacketNode(new NSI("iq", Utilities.SERVER_NAMESPACE), Packet.class);

        p.setFrom(callSession.jabberLocal);
        p.setTo(callSession.jabberRemote);

        p.setID(Long.toString(++this.idNum));
        p.setAttributeValue("type", "set");

        // Jingle
        if (clientJingle) {
            StreamElement jin = p.addElement(new NSI("jingle", "urn:xmpp:jingle:1"));

            jin.setAttributeValue("action", "session-terminate");
            jin.setAttributeValue("sid", callSession.jabberSessionId);
            jin.addElement(new NSI("call-ended", "http://www.google.com/session/phone"));
            StreamElement content = jin.addElement("reason");
            content.addElement("success");
        }

        // Gingle
        StreamElement session = p.addElement(new NSI("session", "http://www.google.com/session"));

        session.setAttributeValue("type", "terminate");
        session.setID(callSession.jabberSessionId);
        session.setAttributeValue("initiator", callSession.jabberInitiator);

        try {
            sendPacket(p);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Error sending BYE", e);
            return false;
        }
        return true;
    }

    public boolean sendPresence(Presence pres) {
        if (pres == null) {
            return false;
        }

        Packet p = conn.getDataFactory().createPacketNode(new NSI("presence", Utilities.SERVER_NAMESPACE),
                Packet.class);

        if (pres.type != null && pres.type.equals("closed")) {
            p.setAttributeValue("type", "unavailable");
        }

        JID from;
        if (pres.resource != null) {
            from = new JID(pres.from + "@" + host + "/" + pres.resource);
        } else {
            from = new JID(pres.from + "@" + host);
        }

        JID to = UriMappings.toJID(pres.to);

        p.setFrom(from);
        p.setTo(to);

        StreamElement caps = conn.getDataFactory()
                .createElementNode(new NSI("c", "http://jabber.org/protocol/caps"));
        String features_ext = "voice-v1";

        if (featSMS) {
            features_ext = "sms-v1 " + features_ext;
        }

        if (featPMUC) {
            features_ext = "pmuc-v1 " + features_ext;
        }
        if (featVID) {
            features_ext = features_ext + " video-v1 camera-v1";
        }

        caps.setAttributeValue("ext", features_ext);
        caps.setAttributeValue("node", clientName);
        caps.setAttributeValue("ver", clientVersion);
        p.add(caps);

        if (pres.show != null) {
            StreamElement show = conn.getDataFactory()
                    .createElementNode(new NSI("show", Utilities.SERVER_NAMESPACE));
            show.addText(pres.show);
            p.add(show);
        }

        if (pres.note != null) {
            StreamElement status = conn.getDataFactory()
                    .createElementNode(new NSI("status", Utilities.SERVER_NAMESPACE));
            status.addText(pres.note);
            p.add(status);
        }

        if (pres.resource != null && pres.resource.equals("KelpiePhone")) {
            StreamElement priority = conn.getDataFactory()
                    .createElementNode(new NSI("priority", Utilities.SERVER_NAMESPACE));
            priority.addText(clientPriority);
            p.add(priority);
        } else {
            StreamElement priority = conn.getDataFactory()
                    .createElementNode(new NSI("priority", Utilities.SERVER_NAMESPACE));
            priority.addText("1");
            p.add(priority);
        }

        StreamElement vCard = conn.getDataFactory().createElementNode(new NSI("x", "vcard-temp:x:update"));
        StreamElement photo = vCard.addElement("photo");
        photo.addText(iconHash);

        p.add(vCard);

        try {
            sendPacket(p);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Error sending presence", e);
            return false;
        }

        if (pres.resource != null && !pres.resource.equals("KelpiePhone")) {
            p = conn.getDataFactory().createPacketNode(new NSI("presence", Utilities.SERVER_NAMESPACE),
                    Packet.class);

            p.setAttributeValue("type", "unavailable");

            from = new JID(pres.from + "@" + host + "/KelpiePhone");
            p.setFrom(from);
            p.setTo(to);

            try {
                sendPacket(p);
            } catch (StreamException e) {
                logger.error("[[" + internalCallId + "]] Error sending presence", e);
                return false;
            }
        }
        return true;
    }

    public boolean sendMessageMessage(MessageMessage mm) {
        JID from = new JID(mm.from + "@" + host);
        JID to = UriMappings.toJID(mm.to);

        if (to == null) {
            logger.error("[[" + internalCallId + "]] No mapping for to destination : " + mm.to);
            return false;
        }

        Packet p = conn.getDataFactory().createPacketNode(new NSI("message", Utilities.SERVER_NAMESPACE),
                Packet.class);
        p.setFrom(from);
        p.setTo(to);
        p.addElement("body");
        p.getFirstElement("body").addText(mm.body);

        p.setAttributeValue("type", "chat");

        if (featNICK) {
            String nick = from.toString().split("@")[0];
            if (nick.contains("+")) {
                nick = nick.split("+")[0];
            }
            p.addElement(new NSI("nick", "http://jabber.org/protocol/nick"));
            p.getFirstElement("nick").addText(nick);
        }

        if (mm.subject != null) {
            p.addElement("subject");
            p.getFirstElement("subject").addText(mm.subject);
        }

        if (mm.thread != null) {
            p.addElement("thread");
            p.getFirstElement("thread").addText(mm.thread);
        }

        try {
            sendPacket(p);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Error sending IM", e);
            return false;
        }
        return true;
    }

    // XEP-0224
    public boolean sendMessageHeadline(MessageMessage mm) {
        JID from = new JID(mm.from + "@" + host);
        JID to = UriMappings.toJID(mm.to);

        if (to == null) {
            logger.error("[[" + internalCallId + "]] No mapping for destination : " + mm.to);
            return false;
        }

        Packet p = conn.getDataFactory().createPacketNode(new NSI("message", Utilities.SERVER_NAMESPACE),
                Packet.class);
        p.setFrom(from);
        p.setTo(to);
        p.setAttributeValue("type", "headline");

        p.addElement(new NSI("attention", "urn:xmpp:attention:0"));
        p.addElement("body");
        p.getFirstElement("body").addText(mm.body);

        try {
            sendPacket(p);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Error sending HEADLINE", e);
            return false;
        }
        return true;
    }

    public void statusChanged(StreamStatusEvent evt) {
        Stream conn = evt.getStream();
        StreamContext ctx = evt.getContext();

        try {
            if (ctx.isInbound()) {
                if (evt.getNextStatus() == Stream.OPENED) {
                    // Finish opening
                    String ns = conn.getDefaultNamespace();

                    conn.getOutboundContext().setFrom(new JID(host));
                    conn.open();

                    // Validate name
                    if (logger.isDebugEnabled()) {
                        logger.debug("[[" + internalCallId + "]] namespace == " + ns);
                    }
                    if (ctx.getNamespaceByURI(ns) == null) {
                        StreamError err = ctx.getDataFactory()
                                .createStreamError(StreamError.INVALID_NAMESPACE_CONDITION);

                        conn.close(new StreamException(err));
                    }
                } else if (evt.getNextStatus() == Stream.CLOSED || evt.getNextStatus() == Stream.DISCONNECTED) {
                    // Finish disconnecting
                    conn.close();
                }
            } else if (ctx.isOutbound()) {
                if (evt.getPreviousStatus() == Stream.OPENED && evt.getNextStatus() == Stream.CLOSED) {
                    conn.disconnect();
                }
            }
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Error changing status", e);
        }
    }

    public void run() {
        Selector sel = null;

        logger.info("[[" + internalCallId + "]] Session thread started: " + this.socketChannel.socket().toString());

        try {
            sel = SelectorProvider.provider().openSelector();
            socketChannel.register(sel, SelectionKey.OP_READ, this);

            while (true) {
                if (sel.select() >= 0) {
                    Iterator<SelectionKey> itr = sel.selectedKeys().iterator();
                    while (itr.hasNext()) {
                        SelectionKey key = itr.next();
                        itr.remove();
                        if (key.isReadable()) {
                            this.execute();
                        }
                    }
                } else {
                    logger.error("[[" + internalCallId + "]] Select returned error");
                }
                if (conn.getCurrentStatus().isDisconnected()) {
                    break;
                }
            }

            logger.info("[[" + internalCallId + "]] Session Connection finished: "
                    + this.socketChannel.socket().toString());
            sel.close();
        } catch (IOException e) {
            logger.error("[[" + internalCallId + "]] Error in xmpp session thread", e);
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Error in xmpp session thread", e);
        } catch (Exception e) {
            logger.error("[[" + internalCallId + "]] Error in xmpp session thread", e);
        } finally {
            try {
                if (sel != null) {
                    sel.close();
                }
            } catch (IOException e) {
                // we are dead already, RIP
            }
        }

        // make sure the connection is closed
        try {
            conn.close();
            conn.disconnect();
            try {
                socketChannel.socket().shutdownInput();
            } catch (IOException e) {
                // ignore
            }
            try {
                socketChannel.socket().shutdownOutput();
            } catch (IOException e) {
                // ignore
            }
            try {
                socketChannel.close();
            } catch (IOException e) {
                // ignore
            }
        } catch (StreamException e) {
            logger.error("[[" + internalCallId + "]] Problem closing stream", e);
        }
    }

}