package org.xmpp.jnodes.smack;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.packet.DiscoverItems;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.xmpp.jnodes.RelayChannel;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class SmackServiceNode implements ConnectionListener, PacketListener {
private final XMPPConnection connection;
private final ConcurrentHashMap<String, RelayChannel> channels = new ConcurrentHashMap<String, RelayChannel>();
private final Map<String, TrackerEntry> trackerEntries = Collections.synchronizedMap(new LinkedHashMap<String, TrackerEntry>());
private long timeout = 60000;
private final static ExecutorService executorService = Executors.newCachedThreadPool();
private final ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(1);
private final AtomicInteger ids = new AtomicInteger(0);
static {
ProviderManager.getInstance().addIQProvider(JingleChannelIQ.NAME, JingleChannelIQ.NAMESPACE, new JingleNodesProvider());
ProviderManager.getInstance().addIQProvider(JingleTrackerIQ.NAME, JingleTrackerIQ.NAMESPACE, new JingleTrackerProvider());
}
public SmackServiceNode(final XMPPConnection connection, final long timeout) {
this.connection = connection;
this.timeout = timeout;
setup();
}
public SmackServiceNode(final String server, final int port, final long timeout) {
final ConnectionConfiguration conf = new ConnectionConfiguration(server, port, server);
conf.setSASLAuthenticationEnabled(false);
conf.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);
connection = new XMPPConnection(conf);
this.timeout = timeout;
}
public void connect(final String user, final String password) throws XMPPException {
connect(user, password, false, Roster.SubscriptionMode.accept_all);
}
public void connect(final String user, final String password, final boolean tryCreateAccount, final Roster.SubscriptionMode mode) throws XMPPException {
connection.connect();
connection.addConnectionListener(this);
if (tryCreateAccount) {
try {
connection.getAccountManager().createAccount(user, password);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// Do Nothing
}
} catch (final XMPPException e) {
// Do Nothing as account may exists
}
}
connection.login(user, password);
connection.getRoster().setSubscriptionMode(mode);
setup();
}
private void setup() {
scheduledExecutor.scheduleWithFixedDelay(new Runnable() {
public void run() {
for (final RelayChannel c : channels.values()) {
final long current = System.currentTimeMillis();
final long da = current - c.getLastReceivedTimeA();
final long db = current - c.getLastReceivedTimeB();
if (da > timeout || db > timeout) {
removeChannel(c);
}
}
}
}, timeout, timeout, TimeUnit.MILLISECONDS);
connection.addPacketListener(this, new PacketFilter() {
public boolean accept(Packet packet) {
return packet instanceof JingleChannelIQ || packet instanceof JingleTrackerIQ;
}
});
}
public void connectionClosed() {
closeAllChannels();
scheduledExecutor.shutdownNow();
}
private void closeAllChannels() {
for (final RelayChannel c : channels.values()) {
removeChannel(c);
}
}
private void removeChannel(final RelayChannel c) {
channels.remove(c.getAttachment());
c.close();
}
public void connectionClosedOnError(Exception e) {
closeAllChannels();
}
public void reconnectingIn(int i) {
}
public void reconnectionSuccessful() {
}
public void reconnectionFailed(Exception e) {
}
protected IQ createUdpChannel(final JingleChannelIQ iq) {
try {
final RelayChannel rc = RelayChannel.createLocalRelayChannel("0.0.0.0", 10000, 40000);
final int id = ids.incrementAndGet();
final String sId = String.valueOf(id);
rc.setAttachment(sId);
channels.put(sId, rc);
final JingleChannelIQ result = new JingleChannelIQ();
result.setType(IQ.Type.RESULT);
result.setTo(iq.getFrom());
result.setFrom(iq.getTo());
result.setPacketID(iq.getPacketID());
result.setHost(rc.getIp());
result.setLocalport(rc.getPortA());
result.setRemoteport(rc.getPortB());
result.setId(sId);
return result;
} catch (IOException e) {
e.printStackTrace();
return JingleChannelIQ.createEmptyError();
}
}
public void processPacket(final Packet packet) {
System.out.println("Received: " + packet.toXML());
if (packet instanceof JingleChannelIQ) {
final JingleChannelIQ request = (JingleChannelIQ) packet;
if (request.isRequest()) {
connection.sendPacket(createUdpChannel(request));
}
} else if (packet instanceof JingleTrackerIQ) {
final JingleTrackerIQ iq = (JingleTrackerIQ) packet;
if (iq.isRequest()) {
final JingleTrackerIQ result = createKnownNodes();
result.setPacketID(packet.getPacketID());
result.setFrom(packet.getTo());
result.setTo(packet.getFrom());
connection.sendPacket(result);
}
}
}
public XMPPConnection getConnection() {
return connection;
}
public static JingleChannelIQ getChannel(final XMPPConnection xmppConnection, final String serviceNode) {
if (xmppConnection == null || !xmppConnection.isConnected()) {
return null;
}
final JingleChannelIQ iq = new JingleChannelIQ();
iq.setFrom(xmppConnection.getUser());
iq.setTo(serviceNode);
PacketCollector collector = xmppConnection.createPacketCollector(new PacketIDFilter(iq.getPacketID()));
xmppConnection.sendPacket(iq);
JingleChannelIQ result = (JingleChannelIQ) collector.nextResult(Math.round(SmackConfiguration.getPacketReplyTimeout() * 1.5));
collector.cancel();
return result;
}
public static JingleTrackerIQ getServices(final XMPPConnection xmppConnection, final String serviceNode) {
if (xmppConnection == null || !xmppConnection.isConnected()) {
return null;
}
final JingleTrackerIQ iq = new JingleTrackerIQ();
iq.setFrom(xmppConnection.getUser());
iq.setTo(serviceNode);
PacketCollector collector = xmppConnection.createPacketCollector(new PacketIDFilter(iq.getPacketID()));
xmppConnection.sendPacket(iq);
Packet result = collector.nextResult(Math.round(SmackConfiguration.getPacketReplyTimeout() * 1.5));
collector.cancel();
return result instanceof JingleTrackerIQ ? (JingleTrackerIQ) result : null;
}
private static void deepSearch(final XMPPConnection xmppConnection, final int maxEntries, final String startPoint, final MappedNodes mappedNodes, final int maxDepth, final int maxSearchNodes, final String protocol, final ConcurrentHashMap<String, String> visited) {
if (xmppConnection == null || !xmppConnection.isConnected()) {
return;
}
if (mappedNodes.getRelayEntries().size() > maxEntries || maxDepth <= 0) {
return;
}
if (startPoint.equals(xmppConnection.getUser())) {
return;
}
if (visited.size() > maxSearchNodes) {
return;
}
JingleTrackerIQ result = getServices(xmppConnection, startPoint);
visited.put(startPoint, startPoint);
if (result != null && result.getType().equals(IQ.Type.RESULT)) {
for (final TrackerEntry entry : result.getEntries()) {
if (entry.getType().equals(TrackerEntry.Type.tracker)) {
mappedNodes.getTrackerEntries().put(entry.getJid(), entry);
deepSearch(xmppConnection, maxEntries, entry.getJid(), mappedNodes, maxDepth - 1, maxSearchNodes, protocol, visited);
} else if (entry.getType().equals(TrackerEntry.Type.relay)) {
if (protocol == null || protocol.equals(entry.getProtocol())) {
mappedNodes.getRelayEntries().put(entry.getJid(), entry);
}
}
}
}
}
public static MappedNodes aSyncSearchServices(final XMPPConnection xmppConnection, final int maxEntries, final int maxDepth, final int maxSearchNodes, final String protocol, final boolean searchBuddies) {
final MappedNodes mappedNodes = new MappedNodes();
final Runnable bgTask = new Runnable(){
@Override
public void run() {
searchServices(new ConcurrentHashMap<String, String>(), xmppConnection, maxEntries, maxDepth, maxSearchNodes, protocol, searchBuddies, mappedNodes);
}
};
executorService.submit(bgTask);
return mappedNodes;
}
public static MappedNodes searchServices(final XMPPConnection xmppConnection, final int maxEntries, final int maxDepth, final int maxSearchNodes, final String protocol, final boolean searchBuddies) {
return searchServices(new ConcurrentHashMap<String, String>(), xmppConnection, maxEntries, maxDepth, maxSearchNodes, protocol, searchBuddies, new MappedNodes());
}
private static MappedNodes searchServices(final ConcurrentHashMap<String, String> visited, final XMPPConnection xmppConnection, final int maxEntries, final int maxDepth, final int maxSearchNodes, final String protocol, final boolean searchBuddies, final MappedNodes mappedNodes) {
if (xmppConnection == null || !xmppConnection.isConnected()) {
return null;
}
searchDiscoItems(xmppConnection, maxEntries, xmppConnection.getServiceName(), mappedNodes, maxDepth - 1, maxSearchNodes, protocol, visited);
// Request to Server
deepSearch(xmppConnection, maxEntries, xmppConnection.getHost(), mappedNodes, maxDepth - 1, maxSearchNodes, protocol, visited);
// Request to Buddies
if (xmppConnection.getRoster() != null && searchBuddies) {
for (final RosterEntry re : xmppConnection.getRoster().getEntries()) {
for (final Iterator<Presence> i = xmppConnection.getRoster().getPresences(re.getUser()); i.hasNext();) {
final Presence presence = i.next();
if (presence.isAvailable()) {
deepSearch(xmppConnection, maxEntries, presence.getFrom(), mappedNodes, maxDepth - 1, maxSearchNodes, protocol, visited);
}
}
}
}
return mappedNodes;
}
private static void searchDiscoItems(final XMPPConnection xmppConnection, final int maxEntries, final String startPoint, final MappedNodes mappedNodes, final int maxDepth, final int maxSearchNodes, final String protocol, final ConcurrentHashMap<String, String> visited) {
final DiscoverItems items = new DiscoverItems();
items.setTo(startPoint);
PacketCollector collector = xmppConnection.createPacketCollector(new PacketIDFilter(items.getPacketID()));
xmppConnection.sendPacket(items);
DiscoverItems result = (DiscoverItems) collector.nextResult(Math.round(SmackConfiguration.getPacketReplyTimeout() * 1.5));
if (result != null) {
final Iterator<DiscoverItems.Item> i = result.getItems();
for (DiscoverItems.Item item = i.hasNext() ? i.next() : null; item != null; item = i.hasNext() ? i.next() : null) {
deepSearch(xmppConnection, maxEntries, item.getEntityID(), mappedNodes, maxDepth, maxSearchNodes, protocol, visited);
}
}
collector.cancel();
}
public static class MappedNodes {
final Map<String, TrackerEntry> relayEntries = Collections.synchronizedMap(new LinkedHashMap<String, TrackerEntry>());
final Map<String, TrackerEntry> trackerEntries = Collections.synchronizedMap(new LinkedHashMap<String, TrackerEntry>());
public Map<String, TrackerEntry> getRelayEntries() {
return relayEntries;
}
public Map<String, TrackerEntry> getTrackerEntries() {
return trackerEntries;
}
}
ConcurrentHashMap<String, RelayChannel> getChannels() {
return channels;
}
public JingleTrackerIQ createKnownNodes() {
final JingleTrackerIQ iq = new JingleTrackerIQ();
iq.setType(IQ.Type.RESULT);
for (final TrackerEntry entry : trackerEntries.values()) {
if (!entry.getPolicy().equals(TrackerEntry.Policy._roster)) {
iq.addEntry(entry);
}
}
return iq;
}
public void addTrackerEntry(final TrackerEntry entry) {
trackerEntries.put(entry.getJid(), entry);
}
public void addEntries(final MappedNodes entries) {
for (final TrackerEntry t : entries.getRelayEntries().values()) {
addTrackerEntry(t);
}
for (final TrackerEntry t : entries.getTrackerEntries().values()) {
addTrackerEntry(t);
}
}
public Map<String, TrackerEntry> getTrackerEntries() {
return trackerEntries;
}
public TrackerEntry getPreferedRelay() {
for (final TrackerEntry trackerEntry : trackerEntries.values()) {
if (TrackerEntry.Type.relay.equals(trackerEntry.getType())) {
return trackerEntry;
}
}
return null;
}
}