1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-09-10 18:59:41 +02:00

Merge from 3.3 branch

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@13663 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
rcollier 2013-05-18 16:56:52 +00:00
commit dac68c64a9
163 changed files with 2304 additions and 2366 deletions

View file

@ -178,12 +178,12 @@ public abstract class Connection {
protected SmackDebugger debugger = null;
/**
* The Reader which is used for the {@see debugger}.
* The Reader which is used for the debugger.
*/
protected Reader reader;
/**
* The Writer which is used for the {@see debugger}.
* The Writer which is used for the debugger.
*/
protected Writer writer;

View file

@ -40,18 +40,11 @@ import java.util.concurrent.BlockingQueue;
class PacketWriter {
private Thread writerThread;
private Thread keepAliveThread;
private Writer writer;
private XMPPConnection connection;
private final BlockingQueue<Packet> queue;
volatile boolean done;
/**
* Timestamp when the last stanza was sent to the server. This information is used
* by the keep alive process to only send heartbeats when the connection has been idle.
*/
private long lastActive = System.currentTimeMillis();
/**
* Creates a new packet writer with the specified connection.
*
@ -117,25 +110,6 @@ class PacketWriter {
writerThread.start();
}
/**
* Starts the keep alive process. A white space (aka heartbeat) is going to be
* sent to the server every 30 seconds (by default) since the last stanza was sent
* to the server.
*/
void startKeepAliveProcess() {
// Schedule a keep-alive task to run if the feature is enabled. will write
// out a space character each time it runs to keep the TCP/IP connection open.
int keepAliveInterval = SmackConfiguration.getKeepAliveInterval();
if (keepAliveInterval > 0) {
KeepAliveTask task = new KeepAliveTask(keepAliveInterval);
keepAliveThread = new Thread(task);
task.setThread(keepAliveThread);
keepAliveThread.setDaemon(true);
keepAliveThread.setName("Smack Keep Alive (" + connection.connectionCounterValue + ")");
keepAliveThread.start();
}
}
void setWriter(Writer writer) {
this.writer = writer;
}
@ -149,9 +123,6 @@ class PacketWriter {
synchronized (queue) {
queue.notifyAll();
}
// Interrupt the keep alive thread if one was created
if (keepAliveThread != null)
keepAliveThread.interrupt();
}
/**
@ -191,13 +162,10 @@ class PacketWriter {
while (!done && (writerThread == thisThread)) {
Packet packet = nextPacket();
if (packet != null) {
synchronized (writer) {
writer.write(packet.toXML());
if (queue.isEmpty()) {
writer.flush();
// Keep track of the last time a stanza was sent to the server
lastActive = System.currentTimeMillis();
}
writer.write(packet.toXML());
if (queue.isEmpty()) {
writer.flush();
}
}
}
@ -205,13 +173,11 @@ class PacketWriter {
// we won't have time to entirely flush it before the socket is forced closed
// by the shutdown process.
try {
synchronized (writer) {
while (!queue.isEmpty()) {
Packet packet = queue.remove();
writer.write(packet.toXML());
}
writer.flush();
while (!queue.isEmpty()) {
Packet packet = queue.remove();
writer.write(packet.toXML());
}
writer.flush();
}
catch (Exception e) {
e.printStackTrace();
@ -268,54 +234,4 @@ class PacketWriter {
writer.write(stream.toString());
writer.flush();
}
/**
* A TimerTask that keeps connections to the server alive by sending a space
* character on an interval.
*/
private class KeepAliveTask implements Runnable {
private int delay;
private Thread thread;
public KeepAliveTask(int delay) {
this.delay = delay;
}
protected void setThread(Thread thread) {
this.thread = thread;
}
public void run() {
try {
// Sleep a minimum of 15 seconds plus delay before sending first heartbeat. This will give time to
// properly finish TLS negotiation and then start sending heartbeats.
Thread.sleep(15000 + delay);
}
catch (InterruptedException ie) {
// Do nothing
}
while (!done && keepAliveThread == thread) {
synchronized (writer) {
// Send heartbeat if no packet has been sent to the server for a given time
if (System.currentTimeMillis() - lastActive >= delay) {
try {
writer.write(" ");
writer.flush();
}
catch (Exception e) {
// Do nothing
}
}
}
try {
// Sleep until we should write the next keep-alive.
Thread.sleep(delay);
}
catch (InterruptedException ie) {
// Do nothing
}
}
}
}
}

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -315,8 +315,13 @@ public class SASLAuthentication implements UserAuthentication {
currentMechanism = constructor.newInstance(this);
// Trigger SASL authentication with the selected mechanism. We use
// connection.getHost() since GSAPI requires the FQDN of the server, which
// may not match the XMPP domain.
currentMechanism.authenticate(username, connection.getServiceName(), password);
// may not match the XMPP domain.
//The serviceName is basically the value that XMPP server sends to the client as being the location
//of the XMPP service we are trying to connect to. This should have the format: host [ "/" serv-name ]
//as per RFC-2831 guidelines
String serviceName = connection.getServiceName();
currentMechanism.authenticate(username, connection.getHost(), serviceName, password);
// Wait until SASL negotiation finishes
synchronized (this) {
@ -383,7 +388,7 @@ public class SASLAuthentication implements UserAuthentication {
public String authenticateAnonymously() throws XMPPException {
try {
currentMechanism = new SASLAnonymous(this);
currentMechanism.authenticate(null,null,"");
currentMechanism.authenticate(null,null,null,"");
// Wait until SASL negotiation finishes
synchronized (this) {

View file

@ -48,7 +48,7 @@ import org.xmlpull.v1.XmlPullParser;
*/
public final class SmackConfiguration {
private static final String SMACK_VERSION = "3.2.2";
private static final String SMACK_VERSION = "3.3.0";
private static int packetReplyTimeout = 5000;
private static int keepAliveInterval = 30000;
@ -58,11 +58,6 @@ public final class SmackConfiguration {
private static int localSocks5ProxyPort = 7777;
private static int packetCollectorSize = 5000;
/**
* defaultPingInterval (in seconds)
*/
private static int defaultPingInterval = 1800; // 30 min (30*60)
/**
* This automatically enables EntityCaps for new connections if it is set to true
*/
@ -117,8 +112,8 @@ public final class SmackConfiguration {
else if (parser.getName().equals("packetCollectorSize")) {
packetCollectorSize = parseIntProperty(parser, packetCollectorSize);
}
else if (parser.getName().equals("defaultPingInterval")) {
defaultPingInterval = parseIntProperty(parser, defaultPingInterval);
else if (parser.getName().equals("autoEnableEntityCaps")) {
autoEnableEntityCaps = Boolean.parseBoolean(parser.nextText());
}
else if (parser.getName().equals("autoEnableEntityCaps")) {
autoEnableEntityCaps = Boolean.parseBoolean(parser.nextText());
@ -320,21 +315,12 @@ public final class SmackConfiguration {
}
/**
* Returns the default ping interval (seconds)
* Set if Entity Caps are enabled or disabled for every new connection
*
* @return
* @param true if Entity Caps should be auto enabled, false if not
*/
public static int getDefaultPingInterval() {
return defaultPingInterval;
}
/**
* Sets the default ping interval (seconds). Set it to '-1' to disable the periodic ping
*
* @param defaultPingInterval
*/
public static void setDefaultPingInterval(int defaultPingInterval) {
SmackConfiguration.defaultPingInterval = defaultPingInterval;
public static void setAutoEnableEntityCaps(boolean b) {
autoEnableEntityCaps = b;
}
/**
@ -345,15 +331,6 @@ public final class SmackConfiguration {
return autoEnableEntityCaps;
}
/**
* Set if Entity Caps are enabled or disabled for every new connection
*
* @param true if Entity Caps should be auto enabled, false if not
*/
public static void setAutoEnableEntityCaps(boolean b) {
autoEnableEntityCaps = b;
}
private static void parseClassToLoad(XmlPullParser parser) throws Exception {
String className = parser.nextText();
// Attempt to load the class so that the class can get initialized

View file

@ -0,0 +1,24 @@
package org.jivesoftware.smack;
public enum SmackError {
NO_RESPONSE_FROM_SERVER("No response from server.");
private String message;
private SmackError(String errMessage) {
message = errMessage;
}
public String getErrorMessage() {
return message;
}
public static SmackError getErrorCode(String message) {
for (SmackError code : values()) {
if (code.message.equals(message)) {
return code;
}
}
return null;
}
}

View file

@ -650,10 +650,6 @@ public class XMPPConnection extends Connection {
// Make note of the fact that we're now connected.
connected = true;
// Start keep alive process (after TLS was negotiated - if available)
packetWriter.startKeepAliveProcess();
if (isFirstInitialization) {
// Notify listeners that a new connection has been established
for (ConnectionCreationListener listener : getConnectionCreationListeners()) {

View file

@ -41,10 +41,12 @@ import java.io.PrintWriter;
* @author Matt Tucker
*/
public class XMPPException extends Exception {
private static final long serialVersionUID = 6881651633890968625L;
private StreamError streamError = null;
private XMPPError error = null;
private Throwable wrappedThrowable = null;
private SmackError smackError = null;
/**
* Creates a new XMPPException.
@ -62,6 +64,16 @@ public class XMPPException extends Exception {
super(message);
}
/**
* Creates a new XMPPException with a Smack specific error code.
*
* @param code the root cause of the exception.
*/
public XMPPException(SmackError code) {
super(code.getErrorMessage());
smackError = code;
}
/**
* Creates a new XMPPException with the Throwable that was the root cause of the
* exception.
@ -74,7 +86,7 @@ public class XMPPException extends Exception {
}
/**
* Cretaes a new XMPPException with the stream error that was the root case of the
* Creates a new XMPPException with the stream error that was the root case of the
* exception. When a stream error is received from the server then the underlying
* TCP connection will be closed by the server.
*
@ -144,6 +156,16 @@ public class XMPPException extends Exception {
return error;
}
/**
* Returns the SmackError asscociated with this exception, or <tt>null</tt> if there
* isn't one.
*
* @return the SmackError asscociated with this exception.
*/
public SmackError getSmackError() {
return smackError;
}
/**
* Returns the StreamError asscociated with this exception, or <tt>null</tt> if there
* isn't one. The underlying TCP connection is closed by the server after sending the

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -3,7 +3,7 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
* Copyright 2003 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View file

@ -0,0 +1,312 @@
/**
* Copyright 2012-2013 Florian Schmaus
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.keepalive;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.ping.PingFailedListener;
import org.jivesoftware.smack.ping.packet.Ping;
/**
* Using an implementation of <a href="http://www.xmpp.org/extensions/xep-0199.html">XMPP Ping (XEP-0199)</a>. This
* class provides keepalive functionality with the server that will periodically "ping" the server to maintain and/or
* verify that the connection still exists.
* <p>
* The ping is done at the application level and is therefore protocol agnostic. It will thus work for both standard TCP
* connections as well as BOSH or any other transport protocol. It will also work regardless of whether the server
* supports the Ping extension, since an error response to the ping serves the same purpose as a pong.
*
* @author Florian Schmaus
*/
public class KeepAliveManager {
private static Map<Connection, KeepAliveManager> instances = new HashMap<Connection, KeepAliveManager>();
private static volatile ScheduledExecutorService periodicPingExecutorService;
static {
if (SmackConfiguration.getKeepAliveInterval() > 0) {
Connection.addConnectionCreationListener(new ConnectionCreationListener() {
public void connectionCreated(Connection connection) {
new KeepAliveManager(connection);
}
});
}
}
private Connection connection;
private long pingInterval = SmackConfiguration.getKeepAliveInterval();
private Set<PingFailedListener> pingFailedListeners = Collections.synchronizedSet(new HashSet<PingFailedListener>());
private volatile ScheduledFuture<?> periodicPingTask;
private volatile long lastSuccessfulContact = -1;
/**
* Retrieves a {@link KeepAliveManager} for the specified {@link Connection}, creating one if it doesn't already
* exist.
*
* @param connection
* The connection the manager is attached to.
* @return The new or existing manager.
*/
public synchronized static KeepAliveManager getInstanceFor(Connection connection) {
KeepAliveManager pingManager = instances.get(connection);
if (pingManager == null) {
pingManager = new KeepAliveManager(connection);
instances.put(connection, pingManager);
}
return pingManager;
}
/*
* Start the executor service if it hasn't been started yet.
*/
private synchronized static void enableExecutorService() {
if (periodicPingExecutorService == null) {
periodicPingExecutorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread pingThread = new Thread(runnable, "Smack Keepalive");
pingThread.setDaemon(true);
return pingThread;
}
});
}
}
/*
* Stop the executor service if all monitored connections are disconnected.
*/
private synchronized static void handleDisconnect(Connection con) {
if (periodicPingExecutorService != null) {
instances.remove(con);
if (instances.isEmpty()) {
periodicPingExecutorService.shutdownNow();
periodicPingExecutorService = null;
}
}
}
private KeepAliveManager(Connection connection) {
this.connection = connection;
init();
handleConnect();
}
/*
* Call after every connection to add the packet listener.
*/
private void handleConnect() {
// Listen for all incoming packets and reset the scheduled ping whenever
// one arrives.
connection.addPacketListener(new PacketListener() {
@Override
public void processPacket(Packet packet) {
// reschedule the ping based on this last server contact
lastSuccessfulContact = System.currentTimeMillis();
schedulePingServerTask();
}
}, null);
}
private void init() {
connection.addConnectionListener(new ConnectionListener() {
@Override
public void connectionClosed() {
stopPingServerTask();
handleDisconnect(connection);
}
@Override
public void connectionClosedOnError(Exception arg0) {
stopPingServerTask();
handleDisconnect(connection);
}
@Override
public void reconnectionSuccessful() {
handleConnect();
schedulePingServerTask();
}
@Override
public void reconnectingIn(int seconds) {
}
@Override
public void reconnectionFailed(Exception e) {
}
});
instances.put(connection, this);
schedulePingServerTask();
}
/**
* Sets the ping interval.
*
* @param pingInterval
* The new ping time interval in milliseconds.
*/
public void setPingInterval(long newPingInterval) {
if (pingInterval == newPingInterval)
return;
// Enable the executor service
if (newPingInterval > 0)
enableExecutorService();
pingInterval = newPingInterval;
if (pingInterval < 0) {
stopPinging();
}
else {
schedulePingServerTask();
}
}
/**
* Stops pinging the server. This cannot stop a ping that has already started, but will prevent another from being triggered.
* <p>
* To restart, call {@link #setPingInterval(long)}.
*/
public void stopPinging() {
pingInterval = -1;
stopPingServerTask();
}
/**
* Gets the ping interval.
*
* @return The ping interval in milliseconds.
*/
public long getPingInterval() {
return pingInterval;
}
/**
* Add listener for notification when a server ping fails.
*
* <p>
* Please note that this doesn't necessarily mean that the connection is lost, a slow to respond server could also
* cause a failure due to taking too long to respond and thus causing a reply timeout.
*
* @param listener
* The listener to be called
*/
public void addPingFailedListener(PingFailedListener listener) {
pingFailedListeners.add(listener);
}
/**
* Remove the listener.
*
* @param listener
* The listener to be removed.
*/
public void removePingFailedListener(PingFailedListener listener) {
pingFailedListeners.remove(listener);
}
/**
* Returns the elapsed time (in milliseconds) since the last successful contact with the server
* (i.e. the last time any message was received).
* <p>
* <b>Note</b>: Result is -1 if no message has been received since manager was created and
* 0 if the elapsed time is negative due to a clock reset.
*
* @return Elapsed time since last message was received.
*/
public long getTimeSinceLastContact() {
if (lastSuccessfulContact == -1)
return lastSuccessfulContact;
long delta = System.currentTimeMillis() - lastSuccessfulContact;
return (delta < 0) ? 0 : delta;
}
/**
* Cancels any existing periodic ping task if there is one and schedules a new ping task if pingInterval is greater
* then zero.
*
* This is designed so only one executor is used for scheduling all pings on all connections. This results in only 1 thread used for pinging.
*/
private synchronized void schedulePingServerTask() {
enableExecutorService();
stopPingServerTask();
if (pingInterval > 0) {
periodicPingTask = periodicPingExecutorService.schedule(new Runnable() {
@Override
public void run() {
Ping ping = new Ping();
PacketFilter responseFilter = new PacketIDFilter(ping.getPacketID());
final PacketCollector response = connection.createPacketCollector(responseFilter);
connection.sendPacket(ping);
if (!pingFailedListeners.isEmpty()) {
// Schedule a collector for the ping reply, notify listeners if none is received.
periodicPingExecutorService.schedule(new Runnable() {
@Override
public void run() {
Packet result = response.nextResult(1);
// Stop queuing results
response.cancel();
// The actual result of the reply can be ignored since we only care if we actually got one.
if (result == null) {
for (PingFailedListener listener : pingFailedListeners) {
listener.pingFailed();
}
}
}
}, SmackConfiguration.getPacketReplyTimeout(), TimeUnit.MILLISECONDS);
}
}
}, getPingInterval(), TimeUnit.MILLISECONDS);
}
}
private void stopPingServerTask() {
if (periodicPingTask != null) {
periodicPingTask.cancel(true);
periodicPingTask = null;
}
}
}

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -0,0 +1,27 @@
/**
* Copyright 2012 Florian Schmaus
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.ping;
/**
* Defines the callback used whenever the server ping fails.
*/
public interface PingFailedListener {
/**
* Called when the server ping fails.
*/
void pingFailed();
}

View file

@ -0,0 +1,39 @@
/**
* Copyright 2012 Florian Schmaus
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.ping.packet;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.ping.PingManager;
public class Ping extends IQ {
public static final String NAMESPACE = "urn:xmpp:ping";
public static final String ELEMENT = "ping";
public Ping() {
}
public Ping(String to) {
setTo(to);
setType(IQ.Type.GET);
}
@Override
public String getChildElementXML() {
return "<" + ELEMENT + " xmlns=\'" + NAMESPACE + "\' />";
}
}

View file

@ -0,0 +1,32 @@
/**
* Copyright 2012 Florian Schmaus
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.ping.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.ping.packet.Ping;
import org.jivesoftware.smack.provider.IQProvider;
import org.xmlpull.v1.XmlPullParser;
public class PingProvider implements IQProvider {
public IQ parseIQ(XmlPullParser parser) throws Exception {
// No need to use the ping constructor with arguments. IQ will already
// have filled out all relevant fields ('from', 'to', 'id').
return new Ping();
}
}

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
@ -92,8 +90,7 @@ class HTTPProxySocketFactory
else
{
String password = proxy.getProxyPassword();
proxyLine = "\r\nProxy-Authorization: Basic "
+ new String(StringUtils.encodeBase64(username + ":" + password));
proxyLine = "\r\nProxy-Authorization: Basic " + StringUtils.encodeBase64(username + ":" + password);
}
socket.getOutputStream().write((hostport + " HTTP/1.1\r\nHost: "
+ hostport + proxyLine + "\r\n\r\n").getBytes("UTF-8"));

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -3,8 +3,6 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

View file

@ -51,6 +51,30 @@ import javax.security.sasl.SaslException;
* using the CallbackHandler method.</li>
* <li>{@link #challengeReceived(String)} -- Handle a challenge from the server.</li>
* </ul>
*
* Basic XMPP SASL authentication steps:
* 1. Client authentication initialization, stanza sent to the server (Base64 encoded):
* <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
* 2. Server sends back to the client the challenge response (Base64 encoded)
* sample:
* realm=<sasl server realm>,nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess
* 3. The client responds back to the server (Base 64 encoded):
* sample:
* username=<userid>,realm=<sasl server realm from above>,nonce="OA6MG9tEQGm2hh",
* cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth,
* digest-uri=<digesturi>,
* response=d388dad90d4bbd760a152321f2143af7,
* charset=utf-8,
* authzid=<id>
* 4. The server evaluates if the user is present and contained in the REALM
* if successful it sends: <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> (Base64 encoded)
* if not successful it sends:
* sample:
* <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
* cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
* </challenge>
*
*
* @author Jay Kline
*/
@ -62,37 +86,88 @@ public abstract class SASLMechanism implements CallbackHandler {
protected String password;
protected String hostname;
public SASLMechanism(SASLAuthentication saslAuthentication) {
this.saslAuthentication = saslAuthentication;
}
/**
* Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of
* authentication is not recommended, since it is very inflexable. Use
* authentication is not recommended, since it is very inflexable. Use
* {@link #authenticate(String, String, CallbackHandler)} whenever possible.
*
*
* Explanation of auth stanza:
*
* The client authentication stanza needs to include the digest-uri of the form: xmpp/serverName
* From RFC-2831:
* digest-uri = "digest-uri" "=" digest-uri-value
* digest-uri-value = serv-type "/" host [ "/" serv-name ]
*
* digest-uri:
* Indicates the principal name of the service with which the client
* wishes to connect, formed from the serv-type, host, and serv-name.
* For example, the FTP service
* on "ftp.example.com" would have a "digest-uri" value of "ftp/ftp.example.com"; the SMTP
* server from the example above would have a "digest-uri" value of
* "smtp/mail3.example.com/example.com".
*
* host:
* The DNS host name or IP address for the service requested. The DNS host name
* must be the fully-qualified canonical name of the host. The DNS host name is the
* preferred form; see notes on server processing of the digest-uri.
*
* serv-name:
* Indicates the name of the service if it is replicated. The service is
* considered to be replicated if the client's service-location process involves resolution
* using standard DNS lookup operations, and if these operations involve DNS records (such
* as SRV, or MX) which resolve one DNS name into a set of other DNS names. In this case,
* the initial name used by the client is the "serv-name", and the final name is the "host"
* component. For example, the incoming mail service for "example.com" may be replicated
* through the use of MX records stored in the DNS, one of which points at an SMTP server
* called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be
* "mail3.example.com". If the service is not replicated, or the serv-name is identical to
* the host, then the serv-name component MUST be omitted
*
* digest-uri verification is needed for ejabberd 2.0.3 and higher
*
* @param username the username of the user being authenticated.
* @param host the hostname where the user account resides.
* @param host the hostname where the user account resides.
* @param serviceName the xmpp service location - used by the SASL client in digest-uri creation
* serviceName format is: host [ "/" serv-name ] as per RFC-2831
* @param password the password for this account.
* @throws IOException If a network error occurs while authenticating.
* @throws XMPPException If a protocol error occurs or the user is not authenticated.
*/
public void authenticate(String username, String host, String password) throws IOException, XMPPException {
public void authenticate(String username, String host, String serviceName, String password) throws IOException, XMPPException {
//Since we were not provided with a CallbackHandler, we will use our own with the given
//information
//Set the authenticationID as the username, since they must be the same in this case.
this.authenticationId = username;
this.password = password;
this.hostname = host;
this.hostname = host;
String[] mechanisms = { getName() };
Map<String,String> props = new HashMap<String,String>();
sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this);
Map<String,String> props = new HashMap<String,String>();
sc = Sasl.createSaslClient(mechanisms, username, "xmpp", serviceName, props, this);
authenticate();
}
/**
* Same as {@link #authenticate(String, String, String, String)}, but with the hostname used as the serviceName.
* <p>
* Kept for backward compatibility only.
*
* @param username the username of the user being authenticated.
* @param host the hostname where the user account resides.
* @param password the password for this account.
* @throws IOException If a network error occurs while authenticating.
* @throws XMPPException If a protocol error occurs or the user is not authenticated.
* @deprecated Please use {@link #authenticate(String, String, String, String)} instead.
*/
public void authenticate(String username, String host, String password) throws IOException, XMPPException {
authenticate(username, host, host, password);
}
/**
* Builds and sends the <tt>auth</tt> stanza to the server. The callback handler will handle
* any additional information, such as the authentication ID or realm, if it is needed.
@ -178,7 +253,13 @@ public abstract class SASLMechanism implements CallbackHandler {
pcb.setPassword(password.toCharArray());
} else if(callbacks[i] instanceof RealmCallback) {
RealmCallback rcb = (RealmCallback)callbacks[i];
rcb.setText(hostname);
//Retrieve the REALM from the challenge response that the server returned when the client initiated the authentication
//exchange. If this value is not null or empty, *this value* has to be sent back to the server in the client's response
//to the server's challenge
String text = rcb.getDefaultText();
//The SASL client (sc) created in smack uses rcb.getText when creating the negotiatedRealm to send it back to the server
//Make sure that this value matches the server's realm
rcb.setText(text);
} else if(callbacks[i] instanceof RealmChoiceCallback){
//unused
//RealmChoiceCallback rccb = (RealmChoiceCallback)callbacks[i];
@ -319,5 +400,5 @@ public abstract class SASLMechanism implements CallbackHandler {
stanza.append("</failure>");
return stanza.toString();
}
}
}
}

View file

@ -29,7 +29,7 @@ import java.io.IOException;
*/
public class Base32Encoder implements StringEncoder {
private static Base32Encoder instance;
private static Base32Encoder instance = new Base32Encoder();
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ2345678";
private Base32Encoder() {
@ -37,9 +37,6 @@ public class Base32Encoder implements StringEncoder {
}
public static Base32Encoder getInstance() {
if (instance == null) {
instance = new Base32Encoder();
}
return instance;
}

View file

@ -7,66 +7,9 @@
package org.jivesoftware.smack.util;
/**
* <p>Encodes and decodes to and from Base64 notation.</p>
* <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p>
* <p>Encodes and decodes to and from Base64 notation.</p>
* This code was obtained from <a href="http://iharder.net/base64">http://iharder.net/base64</a></p>
*
* <p>
* Change Log:
* </p>
* <ul>
* <li>v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug
* when using very small files (~< 40 bytes).</li>
* <li>v2.2 - Added some helper methods for encoding/decoding directly from
* one file to the next. Also added a main() method to support command line
* encoding/decoding from one file to the next. Also added these Base64 dialects:
* <ol>
* <li>The default is RFC3548 format.</li>
* <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates
* URL and file name friendly format as described in Section 4 of RFC3548.
* http://www.faqs.org/rfcs/rfc3548.html</li>
* <li>Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates
* URL and file name friendly format that preserves lexical ordering as described
* in http://www.faqs.org/qa/rfcc-1940.html</li>
* </ol>
* Special thanks to Jim Kellerman at <a href="http://www.powerset.com/">http://www.powerset.com/</a>
* for contributing the new Base64 dialects.
* </li>
*
* <li>v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
* some convenience methods for reading and writing to and from files.</li>
* <li>v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
* with other encodings (like EBCDIC).</li>
* <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
* encoded data was a single byte.</li>
* <li>v2.0 - I got rid of methods that used booleans to set options.
* Now everything is more consolidated and cleaner. The code now detects
* when data that's being decoded is gzip-compressed and will decompress it
* automatically. Generally things are cleaner. You'll probably have to
* change some method calls that you were making to support the new
* options format (<tt>int</tt>s that you "OR" together).</li>
* <li>v1.5.1 - Fixed bug when decompressing and decoding to a
* byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.
* Added the ability to "suspend" encoding in the Output Stream so
* you can turn on and off the encoding if you need to embed base64
* data in an otherwise "normal" stream (like an XML file).</li>
* <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself.
* This helps when using GZIP streams.
* Added the ability to GZip-compress objects before encoding them.</li>
* <li>v1.4 - Added helper methods to read/write files.</li>
* <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
* <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
* where last buffer being read, if not completely full, was not returned.</li>
* <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
* <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
* </ul>
*
* <p>
* I am placing this code in the Public Domain. Do with it as you will.
* This software comes with no guarantees or warranties but with
* plenty of well-wishing instead!
* Please visit <a href="http://iharder.net/base64">http://iharder.net/base64</a>
* periodically to check for updates or to contribute improvements.
* </p>
*
* @author Robert Harder
* @author rob@iharder.net
@ -368,33 +311,6 @@ public class Base64
/** Defeats instantiation. */
private Base64(){}
/**
* Encodes or decodes two files from the command line;
* <strong>feel free to delete this method (in fact you probably should)
* if you're embedding this code into a larger program.</strong>
*/
public final static void main( String[] args )
{
if( args.length < 3 ){
usage("Not enough arguments.");
} // end if: args.length < 3
else {
String flag = args[0];
String infile = args[1];
String outfile = args[2];
if( flag.equals( "-e" ) ){
Base64.encodeFileToFile( infile, outfile );
} // end if: encode
else if( flag.equals( "-d" ) ) {
Base64.decodeFileToFile( infile, outfile );
} // end else if: decode
else {
usage( "Unknown flag: " + flag );
} // end else
} // end else
} // end main
/**
* Prints command line usage.
*

View file

@ -16,20 +16,18 @@ package org.jivesoftware.smack.util;
/**
* A Base 64 encoding implementation.
* @author Florian Schmaus
*/
public class Base64Encoder implements StringEncoder {
private static Base64Encoder instance;
private static Base64Encoder instance = new Base64Encoder();
private Base64Encoder() {
// Use getInstance()
}
public static Base64Encoder getInstance() {
if (instance == null) {
instance = new Base64Encoder();
}
return instance;
}

View file

@ -0,0 +1,48 @@
/**
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
/**
* A Base 64 encoding implementation that generates filename and Url safe encodings.
*
* <p>
* Note: This does NOT produce standard Base 64 encodings, but a variant as defined in
* Section 4 of RFC3548:
* <a href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>.
*
* @author Robin Collier
*/
public class Base64FileUrlEncoder implements StringEncoder {
private static Base64FileUrlEncoder instance = new Base64FileUrlEncoder();
private Base64FileUrlEncoder() {
// Use getInstance()
}
public static Base64FileUrlEncoder getInstance() {
return instance;
}
public String encode(String s) {
return Base64.encodeBytes(s.getBytes(), Base64.URL_SAFE);
}
public String decode(String s) {
return new String(Base64.decode(s, Base64.URL_SAFE));
}
}

View file

@ -81,7 +81,12 @@ public class DNSUtil {
* @return List of HostAddress, which encompasses the hostname and port that the
* XMPP server can be reached at for the specified domain.
*/
public static List<HostAddress> resolveXMPPDomain(String domain) {
public static List<HostAddress> resolveXMPPDomain(final String domain) {
if (dnsResolver == null) {
List<HostAddress> addresses = new ArrayList<HostAddress>(1);
addresses.add(new HostAddress(domain, 5222));
return addresses;
}
return resolveDomain(domain, 'c');
}
@ -102,7 +107,12 @@ public class DNSUtil {
* @return List of HostAddress, which encompasses the hostname and port that the
* XMPP server can be reached at for the specified domain.
*/
public static List<HostAddress> resolveXMPPServerDomain(String domain) {
public static List<HostAddress> resolveXMPPServerDomain(final String domain) {
if (dnsResolver == null) {
List<HostAddress> addresses = new ArrayList<HostAddress>(1);
addresses.add(new HostAddress(domain, 5269));
return addresses;
}
return resolveDomain(domain, 's');
}
@ -117,9 +127,6 @@ public class DNSUtil {
}
}
if (dnsResolver == null)
throw new IllegalStateException("No DNS resolver active.");
List<HostAddress> addresses = new ArrayList<HostAddress>();
// Step one: Do SRV lookups

View file

@ -3,7 +3,7 @@
* $Revision$
* $Date$
*
* Copyright 2003-2007 Jive Software.
* Copyright 2013 Robin Collier.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,48 +17,49 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.util;
import java.text.SimpleDateFormat;
/**
* Defines the various date and time profiles used in XMPP along with their associated formats.
* @author Robin Collier
*
*/
public enum DateFormatType
{
XEP_0082_DATE_PROFILE("yyyy-MM-dd"),
XEP_0082_DATETIME_PROFILE("yyyy-MM-dd'T'HH:mm:ssZ"),
XEP_0082_DATETIME_MILLIS_PROFILE("yyyy-MM-dd'T'HH:mm:ss.SSSZ"),
XEP_0082_TIME_PROFILE("hh:mm:ss"),
XEP_0082_TIME_ZONE_PROFILE("hh:mm:ssZ"),
XEP_0082_TIME_MILLIS_PROFILE("hh:mm:ss.SSS"),
XEP_0082_TIME_MILLIS_ZONE_PROFILE("hh:mm:ss.SSSZ"),
XEP_0091_DATETIME("yyyyMMdd'T'HH:mm:ss");
private String formatString;
private DateFormatType(String dateFormat)
{
formatString = dateFormat;
}
/**
* Get the format string as defined in either XEP-0082 or XEP-0091.
* @return The defined string format for the date.
*/
public String getFormatString()
{
return formatString;
}
/**
* Create a {@link SimpleDateFormat} object with the format defined by {@link #getFormatString()}.
* @return A new date formatter.
*/
public SimpleDateFormat createFormatter()
{
return new SimpleDateFormat(getFormatString());
}
}
package org.jivesoftware.smack.util;
import java.text.SimpleDateFormat;
/**
* Defines the various date and time profiles used in XMPP along with their associated formats.
*
* @author Robin Collier
*
*/
public enum DateFormatType {
// @formatter:off
XEP_0082_DATE_PROFILE("yyyy-MM-dd"),
XEP_0082_DATETIME_PROFILE("yyyy-MM-dd'T'HH:mm:ssZ"),
XEP_0082_DATETIME_MILLIS_PROFILE("yyyy-MM-dd'T'HH:mm:ss.SSSZ"),
XEP_0082_TIME_PROFILE("hh:mm:ss"),
XEP_0082_TIME_ZONE_PROFILE("hh:mm:ssZ"),
XEP_0082_TIME_MILLIS_PROFILE("hh:mm:ss.SSS"),
XEP_0082_TIME_MILLIS_ZONE_PROFILE("hh:mm:ss.SSSZ"),
XEP_0091_DATETIME("yyyyMMdd'T'HH:mm:ss");
// @formatter:on
private String formatString;
private DateFormatType(String dateFormat) {
formatString = dateFormat;
}
/**
* Get the format string as defined in either XEP-0082 or XEP-0091.
*
* @return The defined string format for the date.
*/
public String getFormatString() {
return formatString;
}
/**
* Create a {@link SimpleDateFormat} object with the format defined by {@link #getFormatString()}.
*
* @return A new date formatter.
*/
public SimpleDateFormat createFormatter() {
return new SimpleDateFormat(getFormatString());
}
}

View file

@ -17,8 +17,6 @@
*/
package org.jivesoftware.smack.util;
// TODO move StringEncoder, Base64Encoder and Base32Encoder to smack.util
public interface StringEncoder {
/**
* Encodes an string to another representation
@ -26,7 +24,7 @@ public interface StringEncoder {
* @param string
* @return
*/
public String encode(String string);
String encode(String string);
/**
* Decodes an string back to it's initial representation
@ -34,5 +32,5 @@ public interface StringEncoder {
* @param string
* @return
*/
public String decode(String string);
String decode(String string);
}

View file

@ -16,6 +16,7 @@ package org.jivesoftware.smack.util;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.SmackError;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
@ -47,7 +48,7 @@ final public class SyncPacketSend
response.cancel();
if (result == null) {
throw new XMPPException("No response from server.");
throw new XMPPException(SmackError.NO_RESPONSE_FROM_SERVER);
}
else if (result.getError() != null) {
throw new XMPPException(result.getError());

View file

@ -22,18 +22,19 @@ import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
public class DNSJavaResolver extends DNSResolver {
/**
* This implementation uses the <a href="http://www.dnsjava.org/">dnsjava</a> implementation for resolving DNS addresses.
*
*/
public class DNSJavaResolver implements DNSResolver {
private static DNSJavaResolver instance;
private static DNSJavaResolver instance = new DNSJavaResolver();
private DNSJavaResolver() {
}
public static DNSResolver getInstance() {
if (instance == null) {
instance = new DNSJavaResolver();
}
return instance;
}

View file

@ -17,8 +17,17 @@ package org.jivesoftware.smack.util.dns;
import java.util.List;
public abstract class DNSResolver {
/**
* Implementations of this interface define a class that is capable of resolving DNS addresses.
*
*/
public interface DNSResolver {
public abstract List<SRVRecord> lookupSRVRecords(String name);
/**
* Gets a list of service records for the specified service.
* @param name The symbolic name of the service.
* @return The list of SRV records mapped to the service name.
*/
List<SRVRecord> lookupSRVRecords(String name);
}

View file

@ -23,10 +23,10 @@ public class HostAddress {
/**
* Creates a new HostAddress with the given FQDN. The port will be set to the default XMPP client port: 5222
*
* @param fqdn
* @throws IllegalArgumentException
* @param fqdn Fully qualified domain name.
* @throws IllegalArgumentException If the fqdn is null.
*/
public HostAddress(String fqdn) throws IllegalArgumentException {
public HostAddress(String fqdn) {
if (fqdn == null)
throw new IllegalArgumentException("FQDN is null");
if (fqdn.charAt(fqdn.length() - 1) == '.') {
@ -39,7 +39,14 @@ public class HostAddress {
this.port = 5222;
}
public HostAddress(String fqdn, int port) throws IllegalArgumentException {
/**
* Creates a new HostAddress with the given FQDN. The port will be set to the default XMPP client port: 5222
*
* @param fqdn Fully qualified domain name.
* @param port The port to connect on.
* @throws IllegalArgumentException If the fqdn is null or port is out of valid range (0 - 65535).
*/
public HostAddress(String fqdn, int port) {
this(fqdn);
if (port < 0 || port > 65535)
throw new IllegalArgumentException(
@ -60,10 +67,12 @@ public class HostAddress {
this.exception = e;
}
@Override
public String toString() {
return fqdn + ":" + port;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
@ -80,6 +89,13 @@ public class HostAddress {
return port == address.port;
}
@Override
public int hashCode() {
int result = 1;
result = 37 * result + fqdn.hashCode();
return result * 37 + port;
}
public String getErrorMessage() {
String error;
if (exception == null) {

View file

@ -28,12 +28,12 @@ import javax.naming.directory.InitialDirContext;
import org.jivesoftware.smack.util.DNSUtil;
/**
* A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namepsace.
* A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namespace.
*
* @author Florian Schmaus
*
*/
public class JavaxResolver extends DNSResolver {
public class JavaxResolver implements DNSResolver {
private static JavaxResolver instance;
private static DirContext dirContext;
@ -48,14 +48,14 @@ public class JavaxResolver extends DNSResolver {
}
// Try to set this DNS resolver as primary one
DNSUtil.setDNSResolver(maybeGetInstance());
DNSUtil.setDNSResolver(getInstance());
}
private JavaxResolver() {
}
public static DNSResolver maybeGetInstance() {
public static DNSResolver getInstance() {
if (instance == null && isSupported()) {
instance = new JavaxResolver();
}

View file

@ -29,13 +29,13 @@ public class SRVRecord extends HostAddress implements Comparable<SRVRecord> {
/**
* Create a new SRVRecord
*
* @param fqdn
* @param port
* @param priority
* @param weight
* @throws IllegalArgumentException
* @param fqdn Fully qualified domain name
* @param port The connection port
* @param priority Priority of the target host
* @param weight Relative weight for records with same priority
* @throws IllegalArgumentException fqdn is null or any other field is not in valid range (0-65535).
*/
public SRVRecord(String fqdn, int port, int priority, int weight) throws IllegalArgumentException {
public SRVRecord(String fqdn, int port, int priority, int weight) {
super(fqdn, port);
if (weight < 0 || weight > 65535)
throw new IllegalArgumentException(
@ -60,6 +60,7 @@ public class SRVRecord extends HostAddress implements Comparable<SRVRecord> {
return weight;
}
@Override
public int compareTo(SRVRecord other) {
// According to RFC2782,
// "[a] client MUST attempt to contact the target host with the lowest-numbered priority it can reach".
@ -71,6 +72,7 @@ public class SRVRecord extends HostAddress implements Comparable<SRVRecord> {
return res;
}
@Override
public String toString() {
return super.toString() + " prio:" + priority + ":w:" + weight;
}