diff --git a/build.xml b/build.xml index 5b16a0cb8..6af76c542 100644 --- a/build.xml +++ b/build.xml @@ -1322,6 +1322,7 @@ javax.swing.event, javax.swing.border"/> + diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath index 530ac3ae3..4cc4619ad 100644 --- a/ide/eclipse/.classpath +++ b/ide/eclipse/.classpath @@ -63,5 +63,6 @@ + diff --git a/ide/nbproject/project.xml b/ide/nbproject/project.xml index a2ff83780..fd431cdb8 100644 --- a/ide/nbproject/project.xml +++ b/ide/nbproject/project.xml @@ -123,7 +123,7 @@ src lib/felix.jar:lib/jdic-all.jar:lib/bundle/junit.jar:lib/bundle/log4j.jar:lib/bundle/commons-logging.jar:lib/installer-exclude/concurrent.jar:lib/installer-exclude/dict4j.jar:lib/installer-exclude/dnsjava.jar:lib/installer-exclude/jain-sip-api.jar:lib/installer-exclude/jain-sip-ri.jar:lib/installer-exclude/jain-sdp.jar:lib/installer-exclude/jcalendar-1.3.2.jar:lib/installer-exclude/jdic_misc.jar:lib/installer-exclude/jdom.jar:lib/installer-exclude/jmf.jar:lib/installer-exclude/jml-1.0b5.jar:lib/installer-exclude/joscar-client.jar:lib/installer-exclude/joscar-common.jar:lib/installer-exclude/joscar-protocol.jar:lib/installer-exclude/jsocks-klea.jar:lib/installer-exclude/jspeex.jar:lib/installer-exclude/junit.jar:lib/installer-exclude/log4j-1.2.8.jar:lib/installer-exclude/nist-sdp-1.0.jar:lib/installer-exclude/rome-0.9.jar:lib/installer-exclude/smack.jar:lib/installer-exclude/smackx.jar:lib/installer-exclude/ymsg_network_v0_67.jar:lib/installer-exclude/fmj.jar:lib/installer-exclude/jna.jar:lib/installer-exclude/lti-civil-no_s_w_t.jar:lib/installer-exclude/swing-worker-1.2.jar:lib/os-specific/linux/installer-exclude/jmf.jar:lib/os-specific/linux/jdic_stub.jar:lib/os-specific/mac/OrangeExtensions.jar:lib/os-specific/mac/growl4j.jar:lib/os-specific/mac/jdic_stub.jar:lib/os-specific/mac/installer-exclude/jmf.jar:lib/os-specific/mac/installer-exclude/dock.jar:lib/os-specific/windows/jdic_stub.jar:lib/os-specific/windows/installer-exclude/jmf.jar:lib/os-specific/windows/installer-exclude/sound.jar:lib/installer-exclude/aclibico-2.1.jar:lib/installer-exclude/jdic_misc.jar:lib/installer-exclude/pircbot.jar:lib/os-specific/solaris/jdic_stub.jar:lib/os-specific/solaris/installer-exclude/jmf.jar:lib/installer-exclude/jsch-0.1.36.jar:lib/installer-exclude/apache-ant-1.7.0.jar:lib/installer-exclude/izpack-shortcut-link.jar:lib/installer-exclude/jfontchooser-1.0.5.jar:lib/installer-exclude/laf-widget.jar:lib/installer-exclude/transparency.jar:lib/installer-exclude/zrtp4j-light.jar:lib/installer-exclude/lcrypto-jdk16-143.jar:lib/installer-exclude/otr4j.jar:lib/installer-exclude/profiler4j-1.0-beta3-SC.jar:lib/installer-exclude/httpcore-4.0.1.jar:lib/installer-exclude/httpclient-4.0.1.jar:lib/installer-exclude/json-20090723.jar:lib/installer-exclude/ice4j.jar:lib/installer-exclude/dhcp4java-1.00.jar:lib/installer-exclude/jmdns.jar:lib/installer-exclude/jmyspell-core.jar + mode="compile">lib/felix.jar:lib/jdic-all.jar:lib/bundle/junit.jar:lib/bundle/log4j.jar:lib/bundle/commons-logging.jar:lib/installer-exclude/concurrent.jar:lib/installer-exclude/dict4j.jar:lib/installer-exclude/dnsjava.jar:lib/installer-exclude/jain-sip-api.jar:lib/installer-exclude/jain-sip-ri.jar:lib/installer-exclude/jain-sdp.jar:lib/installer-exclude/jcalendar-1.3.2.jar:lib/installer-exclude/jdic_misc.jar:lib/installer-exclude/jdom.jar:lib/installer-exclude/jmf.jar:lib/installer-exclude/jml-1.0b5.jar:lib/installer-exclude/joscar-client.jar:lib/installer-exclude/joscar-common.jar:lib/installer-exclude/joscar-protocol.jar:lib/installer-exclude/jsocks-klea.jar:lib/installer-exclude/jspeex.jar:lib/installer-exclude/junit.jar:lib/installer-exclude/log4j-1.2.8.jar:lib/installer-exclude/nist-sdp-1.0.jar:lib/installer-exclude/rome-0.9.jar:lib/installer-exclude/smack.jar:lib/installer-exclude/smackx.jar:lib/installer-exclude/ymsg_network_v0_67.jar:lib/installer-exclude/fmj.jar:lib/installer-exclude/jna.jar:lib/installer-exclude/lti-civil-no_s_w_t.jar:lib/installer-exclude/swing-worker-1.2.jar:lib/os-specific/linux/installer-exclude/jmf.jar:lib/os-specific/linux/jdic_stub.jar:lib/os-specific/mac/OrangeExtensions.jar:lib/os-specific/mac/growl4j.jar:lib/os-specific/mac/jdic_stub.jar:lib/os-specific/mac/installer-exclude/jmf.jar:lib/os-specific/mac/installer-exclude/dock.jar:lib/os-specific/windows/jdic_stub.jar:lib/os-specific/windows/installer-exclude/jmf.jar:lib/os-specific/windows/installer-exclude/sound.jar:lib/installer-exclude/aclibico-2.1.jar:lib/installer-exclude/jdic_misc.jar:lib/installer-exclude/pircbot.jar:lib/os-specific/solaris/jdic_stub.jar:lib/os-specific/solaris/installer-exclude/jmf.jar:lib/installer-exclude/jsch-0.1.36.jar:lib/installer-exclude/apache-ant-1.7.0.jar:lib/installer-exclude/izpack-shortcut-link.jar:lib/installer-exclude/jfontchooser-1.0.5.jar:lib/installer-exclude/laf-widget.jar:lib/installer-exclude/transparency.jar:lib/installer-exclude/zrtp4j-light.jar:lib/installer-exclude/lcrypto-jdk16-143.jar:lib/installer-exclude/otr4j.jar:lib/installer-exclude/profiler4j-1.0-beta3-SC.jar:lib/installer-exclude/httpcore-4.0.1.jar:lib/installer-exclude/httpclient-4.0.1.jar:lib/installer-exclude/json-20090723.jar:lib/installer-exclude/ice4j.jar:lib/installer-exclude/dhcp4java-1.00.jar:lib/installer-exclude/jmdns.jar:lib/installer-exclude/jmyspell-core.jar:lib/installer-exclude/jnsapi.jar classes 1.5 diff --git a/lib/installer-exclude/ice4j.jar b/lib/installer-exclude/ice4j.jar index c6e7b1372..dd923007d 100644 Binary files a/lib/installer-exclude/ice4j.jar and b/lib/installer-exclude/ice4j.jar differ diff --git a/lib/installer-exclude/jnsapi.jar b/lib/installer-exclude/jnsapi.jar index 337d0d5b9..5d5ece00b 100644 Binary files a/lib/installer-exclude/jnsapi.jar and b/lib/installer-exclude/jnsapi.jar differ diff --git a/lib/installer-exclude/smack.manifest.mf b/lib/installer-exclude/smack.manifest.mf index e74f283fb..6183b3296 100644 --- a/lib/installer-exclude/smack.manifest.mf +++ b/lib/installer-exclude/smack.manifest.mf @@ -39,4 +39,8 @@ Export-Package: org.jivesoftware.smack, org.jivesoftware.smackx.provider, org.jivesoftware.smack.sasl, org.xmlpull.v1, - org.xmlpull.mxp1 + org.xmlpull.mxp1, + org.xmpp.jnodes, + org.xmpp.jnodes.smack, + org.xmpp.jnodes.nio + diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index 2dc82fea1..b9e4a7184 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -14,10 +14,10 @@ # actual text at runtime, place them as you wish # - \ at the end of a line means that the translation is continued # in the next line -# - you cannot use single quotes when a parameter is used in the sentence. +# - you cannot use single quotes when a parameter is used in the sentence. # For example, , , # or does not display correctly. In such cases, you need -# to use double quotes (''): +# to use double quotes (''): # , , or # # @@ -409,7 +409,7 @@ service.gui.TODAY=Today service.gui.TOOLS=&Tools service.gui.TRANSFER=Trans&fer service.gui.TO=&To: -service.gui.TRANSFER_CALL_MSG=Select the name of the contact you would like to transfer to and then click Transfer. +service.gui.TRANSFER_CALL_MSG=Select the name of the contact you would like to transfer to and then click Transfer. service.gui.TRANSFER_CALL_TITLE=Transfer Call service.gui.TRANSFER_CALL_TO=Transfer to: service.gui.TRANSPARENCY_NOT_ENABLED=Transparency is not supported by your current configuration. @@ -708,6 +708,12 @@ plugin.jabberaccregwizz.NO_STUN_ADDRESS=Please fill a valid STUN server address plugin.jabberaccregwizz.NO_STUN_USERNAME=Please fill a valid STUN server username in order to continue. plugin.jabberaccregwizz.STUN_ALREADY_EXIST=The STUN server you specified already exist. plugin.jabberaccregwizz.USE_DEFAULT_STUN_SERVER=Use SIP Communicator's STUN server in case no other servers are available. +plugin.jabberaccregwizz.USE_JINGLE_NODES=Use Jingle Nodes +plugin.jabberaccregwizz.AUTO_DISCOVER_JN=Auto discover Jingle Nodes relays +plugin.jabberaccregwizz.RELAY_SUPPORT=Support relay +plugin.jabberaccregwizz.ADD_JINGLE_NODE=Add Jingle Node +plugin.jabberaccregwizz.JID_ADDRESS=JID Address +plugin.jabberaccregwizz.ADDITIONAL_JINGLE_NODES=Additional Jingle Nodes # mailbox plugin.mailbox.OUTGOING=Outgoing Message: @@ -932,7 +938,7 @@ impl.media.security.DATA_SEND_FAILED=Failed to send encryption data. Inter impl.media.security.SECURITY_OFF=Call encryption support off impl.media.security.SECURITY_ON=Call encryption support on -# ZRTP Configuration +# ZRTP Configuration impl.media.security.zrtp.TITLE=ZRTP impl.media.security.zrtp.PUB_KEYS=Public keys impl.media.security.zrtp.HASHES=Hashes @@ -981,9 +987,9 @@ plugin.securityconfig.masterpassword.CHANGE_MASTER_PASSWORD=Change Master Passwo plugin.securityconfig.masterpassword.USE_MASTER_PASSWORD=Use a master password plugin.securityconfig.masterpassword.SAVED_PASSWORDS=Saved Passwords... plugin.securityconfig.masterpassword.INFO_TEXT=A Master Password is used to protect saved account passwords. Please make sure you remember it. -plugin.securityconfig.masterpassword.CURRENT_PASSWORD=Current password: -plugin.securityconfig.masterpassword.ENTER_PASSWORD=Enter new password: -plugin.securityconfig.masterpassword.REENTER_PASSWORD=Re-enter password: +plugin.securityconfig.masterpassword.CURRENT_PASSWORD=Current password: +plugin.securityconfig.masterpassword.ENTER_PASSWORD=Enter new password: +plugin.securityconfig.masterpassword.REENTER_PASSWORD=Re-enter password: plugin.securityconfig.masterpassword.MP_TITLE=Master Password plugin.securityconfig.masterpassword.MP_NOT_SET=(not set) plugin.securityconfig.masterpassword.MP_CURRENT_EMPTY=You did not enter the correct current Master Password. Please try again. diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java b/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java index a37ced0d7..c190c1e67 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/toolBars/MainToolBar.java @@ -47,6 +47,9 @@ public class MainToolBar ChatSessionChangeListener, Skinnable { + /** + * Serial version UID. + */ private static final long serialVersionUID = -5572510509556499465L; /** @@ -114,7 +117,7 @@ public class MainToolBar /** * The current ChatSession made known to this instance by the last - * call to its {@link #chatChanged(ChatPanel)}. + * call to its {@link #chatChanged(ChatPanel)}. */ private ChatSession chatSession; @@ -503,7 +506,7 @@ public void changeHistoryButtonsState(ChatPanel chatPanel) /** * Sets the current ChatSession made known to this instance by the * last call to its {@link #chatChanged(ChatPanel)}. - * + * * @param chatSession the ChatSession to become current for this * instance */ diff --git a/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf b/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf index 2f0266b93..6d5e1f86c 100644 --- a/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf +++ b/src/net/java/sip/communicator/impl/netaddr/netaddr.manifest.mf @@ -8,11 +8,14 @@ Import-Package: net.java.sip.communicator.service.configuration, net.java.sip.communicator.service.packetlogging, net.java.sip.communicator.util, org.osgi.framework, + org.ice4j.stack, javax.crypto, javax.crypto.spec Export-Package: net.java.sip.communicator.service.netaddr, net.java.sip.communicator.service.netaddr.event, org.ice4j, + org.ice4j.socket, + org.ice4j.stack, org.ice4j.ice, org.ice4j.ice.harvest, org.ice4j.security diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java index d6e672154..b07f71cee 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java @@ -21,6 +21,7 @@ import org.ice4j.ice.*; import org.ice4j.ice.harvest.*; import org.ice4j.security.*; +import org.xmpp.jnodes.smack.*; /** * A {@link TransportManagerJabberImpl} implementation that would use ICE for @@ -79,7 +80,6 @@ public class IceUdpTransportManager public IceUdpTransportManager(CallPeerJabberImpl callPeer) { super(callPeer); - iceAgent = createIceAgent(); } @@ -185,6 +185,24 @@ private Agent createIceAgent() } } + /* Jingle nodes candidate */ + if(accID.isJingleNodesRelayEnabled()) + { + /* this method is blocking until Jingle Nodes auto-discovery (if + * enabled) finished + */ + SmackServiceNode serviceNode = + peer.getProtocolProvider().getJingleNodesServiceNode(); + + JingleNodesHarvester harvester = new JingleNodesHarvester( + serviceNode); + + if(harvester != null) + { + agent.addCandidateHarvester(harvester); + } + } + return agent; } @@ -232,7 +250,8 @@ protected StreamConnector createStreamConnector(MediaType mediaType) * of the MediaStream with the specified MediaType * @throws OperationFailedException if anything goes wrong while * initializing the requested StreamConnector - * @see net.java.sip.communicator.service.protocol.media.TransportManager#getStreamConnector(MediaType) + * @see net.java.sip.communicator.service.protocol.media.TransportManager# + * getStreamConnector(MediaType) */ @Override public StreamConnector getStreamConnector(MediaType mediaType) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JabberAccountID.java b/src/net/java/sip/communicator/impl/protocol/jabber/JabberAccountID.java index e1719d0c2..788231c43 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JabberAccountID.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JabberAccountID.java @@ -14,6 +14,7 @@ * The Jabber implementation of a sip-communicator AccountID * * @author Damian Minkov + * @author Sebastien Vincent */ public class JabberAccountID extends AccountID @@ -94,7 +95,7 @@ public boolean isStunServerDiscoveryEnabled() } /** - * Determines whether this account's provider use the default STUN server + * Determines whether this account's provider uses the default STUN server * provided by SIP Communicator if there is no other STUN/TURN server * discovered/configured. * @@ -108,4 +109,64 @@ public boolean isUseDefaultStunServer() ProtocolProviderFactory.USE_DEFAULT_STUN_SERVER, true); } + + /** + * Returns the list of JingleNodes trackers/relays that this account is + * currently configured to use. + * + * @return the list of JingleNodes trackers/relays that this account is + * currently configured to use. + */ + public List getJingleNodes() + { + Map accountProperties = getAccountProperties(); + List serList + = new ArrayList(); + + for (int i = 0; i < JingleNodeDescriptor.MAX_JN_RELAY_COUNT; i ++) + { + JingleNodeDescriptor node + = JingleNodeDescriptor.loadDescriptor( + accountProperties, + JingleNodeDescriptor.JN_PREFIX + i); + + // If we don't find a relay server with the given index, it means + // that there're no more servers left in the table so we've nothing + // more to do here. + if (node == null) + break; + + serList.add(node); + } + + return serList; + } + + /** + * Determines whether this account's provider is supposed to auto discover + * JingleNodes relay. + * + * @return true if this provider would need to discover JingleNodes + * relay, false otherwise + */ + public boolean isJingleNodesAutoDiscoveryEnabled() + { + return getAccountPropertyBoolean( + ProtocolProviderFactory.AUTO_DISCOVER_JINGLE_NODES, + false); + } + + /** + * Determines whether this account's provider uses JingleNodes relay (if + * available). + * + * @return true if this provider would use JingleNodes relay (if + * available), false otherwise + */ + public boolean isJingleNodesRelayEnabled() + { + return getAccountPropertyBoolean( + ProtocolProviderFactory.IS_USE_JINGLE_NODES, + false); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java new file mode 100644 index 000000000..6d439e993 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidate.java @@ -0,0 +1,117 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.lang.reflect.*; +import java.net.*; + +import org.ice4j.*; +import org.ice4j.ice.*; +import org.ice4j.socket.*; + +/** + * Represents a Candidate obtained via Jingle Nodes. + * + * @author Sebastien Vincent + */ +public class JingleNodesCandidate + extends LocalCandidate +{ + /** + * The socket used to communicate with relay. + */ + private DatagramSocket socket = null; + + /** + * The RelayedCandidateDatagramSocket of this + * JingleNodesCandidate. + */ + private JingleNodesCandidateDatagramSocket + jingleNodesCandidateDatagramSocket = null; + + /** + * TransportAddress of the Jingle Nodes relay where we will send + * our packet. + */ + private TransportAddress localEndPoint = null; + + /** + * Creates a JingleNodesRelayedCandidate for the specified + * transport, address, and base. + * + * @param transportAddress the transport address that this candidate is + * encapsulating. + * @param parentComponent the Component that this candidate + * belongs to. + * @param localEndPoint TransportAddress of the Jingle Nodes relay + * where we will send our packet. + */ + public JingleNodesCandidate(TransportAddress transportAddress, + Component parentComponent, TransportAddress localEndPoint) + { + super(transportAddress, parentComponent, + CandidateType.RELAYED_CANDIDATE); + setBase(this); + setRelayServerAddress(localEndPoint); + this.localEndPoint = localEndPoint; + } + + /** + * Gets the JingleNodesCandidateDatagramSocket of this + * JingleNodesCandidate. + *

+ * Note: The method is part of the internal API of + * RelayedCandidate and TurnCandidateHarvest and is not + * intended for public use. + *

+ * + * @return the RelayedCandidateDatagramSocket of this + * RelayedCandidate + */ + public synchronized JingleNodesCandidateDatagramSocket + getRelayedCandidateDatagramSocket() + { + if (jingleNodesCandidateDatagramSocket == null) + { + try + { + jingleNodesCandidateDatagramSocket + = new JingleNodesCandidateDatagramSocket( + this, localEndPoint); + } + catch (SocketException sex) + { + throw new UndeclaredThrowableException(sex); + } + } + return jingleNodesCandidateDatagramSocket; + } + + /** + * Gets the DatagramSocket associated with this Candidate. + * + * @return the DatagramSocket associated with this + * Candidate + */ + public DatagramSocket getSocket() + { + if (socket == null) + { + try + { + socket + = new MultiplexingDatagramSocket( + getRelayedCandidateDatagramSocket()); + } + catch (SocketException sex) + { + throw new UndeclaredThrowableException(sex); + } + } + return socket; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidateDatagramSocket.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidateDatagramSocket.java new file mode 100644 index 000000000..45579b426 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesCandidateDatagramSocket.java @@ -0,0 +1,136 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.io.IOException; +import java.net.*; + +import org.ice4j.*; + +/** + * Represents an application-purposed (as opposed to an ICE-specific) + * DatagramSocket for a JingleNodesCandidate. + * + * @author Sebastien Vincent + */ +public class JingleNodesCandidateDatagramSocket extends DatagramSocket +{ + /** + * TransportAddress of the Jingle Nodes relay where we will send + * our packet. + */ + private TransportAddress localEndPoint = null; + + /** + * The JingleNodesCandidate. + */ + private JingleNodesCandidate jingleNodesCandidate; + + /** + * Initializes a new JingleNodesdCandidateDatagramSocket instance + * which is to be the socket of a specific + * JingleNodesCandidate. + * + * @param jingleNodesCandidate the JingleNodesCandidate which is to + * use the new instance as the value of its socket property + * @param localEndPoint TransportAddress of the Jingle Nodes relay + * where we will send our packet. + * @throws SocketException if anything goes wrong while initializing the new + * JingleNodesCandidateDatagramSocket instance + */ + public JingleNodesCandidateDatagramSocket( + JingleNodesCandidate jingleNodesCandidate, + TransportAddress localEndPoint) + throws SocketException + { + super(/* bindaddr */ (SocketAddress) null); + this.jingleNodesCandidate = jingleNodesCandidate; + this.localEndPoint = localEndPoint; + } + + /** + * Sends a datagram packet from this socket. The DatagramPacket + * includes information indicating the data to be sent, its length, the IP + * address of the remote host, and the port number on the remote host. + * + * @param p the DatagramPacket to be sent + * @throws IOException if an I/O error occurs + * @see DatagramSocket#send(DatagramPacket) + */ + @Override + public void send(DatagramPacket p) + throws IOException + { + byte data[] = p.getData(); + int dataLen = p.getLength(); + int dataOffset = p.getOffset(); + + /* send to Jingle Nodes relay address on local port */ + DatagramPacket packet = new DatagramPacket(data, dataOffset, dataLen, + new InetSocketAddress(localEndPoint.getAddress(), + localEndPoint.getPort())); + + //XXX reuse an existing DatagramPacket ? + super.send(packet); + } + + /** + * Gets the local address to which the socket is bound. + * JingleNodesCandidateDatagramSocket returns the address + * of its localSocketAddress. + *

+ * If there is a security manager, its checkConnect method is first + * called with the host address and -1 as its arguments to see if + * the operation is allowed. + *

+ * + * @return the local address to which the socket is bound, or an + * InetAddress representing any local address if either the socket + * is not bound, or the security manager checkConnect method does + * not allow the operation + * @see #getLocalSocketAddress() + * @see DatagramSocket#getLocalAddress() + */ + @Override + public InetAddress getLocalAddress() + { + return getLocalSocketAddress().getAddress(); + } + + /** + * Returns the port number on the local host to which this socket is bound. + * JingleNodesCandidateDatagramSocket returns the port of + * its localSocketAddress. + * + * @return the port number on the local host to which this socket is bound + * @see #getLocalSocketAddress() + * @see DatagramSocket#getLocalPort() + */ + @Override + public int getLocalPort() + { + return getLocalSocketAddress().getPort(); + } + + /** + * Returns the address of the endpoint this socket is bound to, or + * null if it is not bound yet. Since + * JingleNodesCandidateDatagramSocket represents an + * application-purposed DatagramSocket relaying data to and from a + * Jingle Nodes relay, the localSocketAddress is the + * transportAddress of respective JingleNodesCandidate. + * + * @return a SocketAddress representing the local endpoint of this + * socket, or null if it is not bound yet + * @see DatagramSocket#getLocalSocketAddress() + */ + @Override + public InetSocketAddress getLocalSocketAddress() + { + return jingleNodesCandidate.getTransportAddress(); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java new file mode 100644 index 000000000..e26439529 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JingleNodesHarvester.java @@ -0,0 +1,181 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.net.DatagramSocket; +import java.util.*; + +import org.ice4j.*; +import org.ice4j.ice.*; +import org.ice4j.ice.harvest.*; + +import org.jivesoftware.smack.*; +import org.xmpp.jnodes.smack.*; + +import net.java.sip.communicator.util.*; + +/** + * Implements a CandidateHarvester which gathers Candidates + * for a specified {@link Component} using Jingle Nodes as defined in + * XEP 278 "Jingle Relay Nodes". + * + * @author Sebastien Vincent + */ +public class JingleNodesHarvester + implements CandidateHarvester +{ + /** + * The Logger used by the JingleNodesHarvester class and + * its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(JingleNodesHarvester.class.getName()); + + /** + * XMPP connection. + */ + private SmackServiceNode serviceNode = null; + + /** + * JingleNodes relay allocate two address/port couple for us. Due to the + * architecture of Ice4j that harvest address for each component, we store + * the second address/port couple. + */ + private TransportAddress localAddressSecond = null; + + /** + * JingleNodes relay allocate two address/port couple for us. Due to the + * architecture of Ice4j that harvest address for each component, we store + * the second address/port couple. + */ + private TransportAddress relayedAddressSecond = null; + + /** + * Constructor. + * + * @param serviceNode the SmackServiceNode + */ + public JingleNodesHarvester(SmackServiceNode serviceNode) + { + this.serviceNode = serviceNode; + } + + /** + * Gathers Jingle Nodes candidates for all host Candidates that are + * already present in the specified component. This method relies + * on the specified component to already contain all its host + * candidates so that it would resolve them. + * + * @param component the {@link Component} that we'd like to gather candidate + * Jingle Nodes Candidates for + * @return the LocalCandidates gathered by this + * CandidateHarvester + */ + public synchronized Collection harvest(Component component) + { + logger.info("harvest Jingle Nodes"); + + Collection candidates = new HashSet(); + String ip = null; + int port = -1; + + /* if we have already a candidate (RTCP) allocated, get it */ + if(localAddressSecond != null && relayedAddressSecond != null) + { + LocalCandidate candidate = createJingleNodesCandidate( + relayedAddressSecond, component, localAddressSecond); + candidates.add(candidate); + component.addLocalCandidate(candidate); + + localAddressSecond = null; + relayedAddressSecond = null; + return candidates; + } + + XMPPConnection conn = serviceNode.getConnection(); + JingleChannelIQ ciq = null; + + if (serviceNode != null) + { + final TrackerEntry preferred = serviceNode.getPreferedRelay(); + + if (preferred != null) + { + ciq = SmackServiceNode.getChannel(conn, preferred.getJid()); + } + } + + if (ciq != null && ciq.getRemoteport() > 0) + { + ip = ciq.getHost(); + port = ciq.getRemoteport(); + + if(logger.isInfoEnabled()) + { + logger.info("JN relay: " + ip + " remote port:" + port + + " local port: " + ciq.getLocalport()); + } + + /* RTP */ + TransportAddress relayedAddress = new TransportAddress(ip, port, + Transport.UDP); + TransportAddress localAddress = new TransportAddress(ip, + ciq.getLocalport(), Transport.UDP); + + LocalCandidate local = createJingleNodesCandidate( + relayedAddress, component, localAddress); + + /* RTCP */ + relayedAddressSecond + = new TransportAddress(ip, port + 1,Transport.UDP); + localAddressSecond + = new TransportAddress(ip, ciq.getLocalport() + 1, + Transport.UDP); + + candidates.add(local); + component.addLocalCandidate(local); + } + return candidates; + } + + /** + * Creates a new JingleNodesRelayedCandidate instance which is to + * represent a specific TransportAddress. + * + * @param transportAddress the TransportAddress allocated by the + * relay + * @param component the Component for which the candidate will be + * added + * @param localEndPoint TransportAddress of the Jingle Nodes relay + * where we will send our packet. + * @return a new JingleNodesRelayedCandidate instance which + * represents the specified TransportAddress + */ + protected JingleNodesCandidate createJingleNodesCandidate( + TransportAddress transportAddress, Component component, + TransportAddress localEndPoint) + { + JingleNodesCandidate cand = null; + + try + { + cand = new JingleNodesCandidate(transportAddress, + component, + localEndPoint); + DatagramSocket stunSocket = cand.getStunSocket(null); + cand.getStunStack().addSocket(stunSocket); + } + catch(Throwable e) + { + logger.debug( + "Exception occurred when creating JingleNodesCandidate: " + + e); + } + + return cand; + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java index ea0e28aa3..c397a5763 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderFactoryJabberImpl.java @@ -20,6 +20,10 @@ public class ProtocolProviderFactoryJabberImpl extends ProtocolProviderFactory { + /** + * Indicates if ICE should be used. + */ + public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED"; /** * Creates an instance of the ProtocolProviderFactoryJabberImpl. @@ -47,13 +51,15 @@ public AccountID installAccount( String userIDStr, BundleContext context = JabberActivator.getBundleContext(); if (context == null) - throw new NullPointerException("The specified BundleContext was null"); + throw new NullPointerException( + "The specified BundleContext was null"); if (userIDStr == null) throw new NullPointerException("The specified AccountID was null"); if (accountProperties == null) - throw new NullPointerException("The specified property map was null"); + throw new NullPointerException( + "The specified property map was null"); accountProperties.put(USER_ID, userIDStr); @@ -89,7 +95,15 @@ public AccountID installAccount( String userIDStr, return accountID; } - protected AccountID createAccountID(String userID, Map accountProperties) + /** + * Create an account. + * + * @param userID the user ID + * @param accountProperties the properties associated with the user ID + * @return new AccountID + */ + protected AccountID createAccountID(String userID, + Map accountProperties) { return new JabberAccountID(userID, accountProperties); } @@ -104,6 +118,13 @@ protected ProtocolProviderService createService(String userID, return service; } + /** + * Modify an existing account. + * + * @param protocolProvider the ProtocolProviderService responsible + * of the account + * @param accountProperties modified properties to be set + */ @Override public void modifyAccount( ProtocolProviderService protocolProvider, Map accountProperties) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index a2bdae599..a41df7fb3 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -31,6 +31,7 @@ import org.jivesoftware.smackx.packet.*; import org.osgi.framework.*; +import org.xmpp.jnodes.smack.*; /** * An implementation of the protocol provider service over the Jabber protocol @@ -91,6 +92,12 @@ public class ProtocolProviderServiceJabberImpl public static final String URN_XMPP_JINGLE_ICE_UDP_1 = IceUdpTransportPacketExtension.NAMESPACE; + /** + * Jingle's Discovery Info URN for Jingle Nodes support. + */ + public static final String URN_XMPP_JINGLE_NODES + = "http://jabber.org/protocol/jinglenodes"; + /** * Jingle's Discover Info URN for "XEP-0251: Jingle Session Transfer" * support. @@ -236,6 +243,16 @@ enum ConnectState */ private SmackPacketDebugger debugger = null; + /** + * Jingle Nodes service. + */ + private SmackServiceNode jingleNodesServiceNode = null; + + /** + * Synchronization object to monitore jingle nodes auto discovery. + */ + private final Object jingleNodesSyncRoot = new Object(); + /** * Returns the state of the registration of this protocol provider * @return the RegistrationState that this provider is @@ -823,6 +840,8 @@ private ConnectState connectAndLogin( logger.error("Failed to publish presence status"); } + startJingleNodesDiscovery(); + return ConnectState.STOP_TRYING; } else @@ -1184,6 +1203,17 @@ protected void initialize(String screenname, supportedFeatures.add(URN_XMPP_JINGLE_RTP_VIDEO); supportedFeatures.add(URN_XMPP_JINGLE_RTP_ZRTP); + /* + * Reflect the preference of the user with respect to the use of + * Jingle Nodes. + */ + if (accountID.getAccountPropertyBoolean( + ProtocolProviderFactoryJabberImpl.IS_USE_JINGLE_NODES, + false)) + { + supportedFeatures.add(URN_XMPP_JINGLE_NODES); + } + /* add extension to support remote control */ supportedFeatures.add(InputEvtIQ.NAMESPACE); @@ -1757,6 +1787,71 @@ public InetAddress getNextHop() return nextHop; } + /** + * Start auto-discovery of JingleNodes tracker/relays. + */ + public void startJingleNodesDiscovery() + { + // Jingle Nodes Service Initialization + JabberAccountID accID = (JabberAccountID)getAccountID(); + jingleNodesServiceNode = new SmackServiceNode(connection, 60000); + + for(JingleNodeDescriptor desc : accID.getJingleNodes()) + { + TrackerEntry entry = new TrackerEntry( + desc.isRelaySupported() ? TrackerEntry.Type.relay : + TrackerEntry.Type.tracker, + TrackerEntry.Policy._public, + desc.getJID(), + JingleChannelIQ.UDP); + + jingleNodesServiceNode.addTrackerEntry(entry); + } + + final SmackServiceNode service = jingleNodesServiceNode; + final boolean autoDiscover = accID.isJingleNodesAutoDiscoveryEnabled(); + + new Thread() + { + public void run() + { + synchronized(jingleNodesSyncRoot) + { + if(logger.isInfoEnabled()) + { + logger.info("Start Jingle Nodes discovery!"); + } + + final SmackServiceNode.MappedNodes nodes = + service.searchServices( + connection, 6, 3, 20, JingleChannelIQ.UDP, + autoDiscover); + + if(logger.isInfoEnabled()) + { + logger.info("Jingle Nodes discovery terminated!"); + } + + service.addEntries(nodes); + } + } + }.start(); + } + + /** + * Get the Jingle Nodes service. Note that this method will block until + * Jingle Nodes auto discovery (if enabled) finished. + * + * @return Jingle Nodes service + */ + public SmackServiceNode getJingleNodesServiceNode() + { + synchronized(jingleNodesSyncRoot) + { + return jingleNodesServiceNode; + } + } + /** * Logs a specific message and associated Throwable cause as an * error using the current Logger and then throws a new diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java index 1e8cf9a16..35fc3bbca 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java @@ -194,8 +194,9 @@ private void removeRemoteContent(String name) * Starts transport candidate harvest. This method should complete rapidly * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests * are necessary, they should be executed in a separate thread. Candidate - * harvest would then need to be concluded in the {@link #wrapupHarvest()} - * method which would be called once we absolutely need the candidates. + * harvest would then need to be concluded in the + * {@link #wrapupCandidateHarvest()} method which would be called once we + * absolutely need the candidates. * * @param ourOffer the content list that should tell us how many stream * connectors we actually need. @@ -232,8 +233,9 @@ public void startCandidateHarvest(List ourOffer) * Starts transport candidate harvest. This method should complete rapidly * and, in case of lengthy procedures like STUN/TURN/UPnP candidate harvests * are necessary, they should be executed in a separate thread. Candidate - * harvest would then need to be concluded in the {@link #wrapupHarvest()} - * method which would be called once we absolutely need the candidates. + * harvest would then need to be concluded in the + * {@link #wrapupCandidateHarvest()} method which would be called once we + * absolutely need the candidates. * * @param theirOffer a media description offer that we've received from the * remote party and that we should use in case we need to know what diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf index 20bd54be3..a48b4644a 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf @@ -6,6 +6,8 @@ Bundle-Version: 0.0.1 System-Bundle: yes Import-Package: org.osgi.framework, org.ice4j, + org.ice4j.socket, + org.ice4j.stack, org.ice4j.ice, org.ice4j.ice.harvest, org.ice4j.security, @@ -51,4 +53,7 @@ Import-Package: org.osgi.framework, javax.xml.parsers, javax.net.ssl, javax.security.sasl, - javax.security.auth.callback + javax.security.auth.callback, + org.xmpp.jnodes, + org.xmpp.jnodes.smack, + org.xmpp.jnodes.nio \ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/jabberaccregwizz/FirstWizardPage.java b/src/net/java/sip/communicator/plugin/jabberaccregwizz/FirstWizardPage.java index d90592392..01d6f2e48 100644 --- a/src/net/java/sip/communicator/plugin/jabberaccregwizz/FirstWizardPage.java +++ b/src/net/java/sip/communicator/plugin/jabberaccregwizz/FirstWizardPage.java @@ -27,6 +27,11 @@ public class FirstWizardPage extends TransparentPanel implements WizardPage { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + public static final String FIRST_PAGE_IDENTIFIER = "FirstPageIdentifier"; private final AccountPanel accountPanel; @@ -180,6 +185,20 @@ public void commitPage() for (StunServerDescriptor descriptor : stunServers) registration.addStunServer(descriptor); + registration.setUseJingleNodes(iceConfigPanel.isUseJingleNodes()); + registration.setAutoDiscoverJingleNodes( + iceConfigPanel.isAutoDiscoverJingleNodes()); + + //we will be reentering all Jingle nodes so let's make sure we clear + //the servers vector in case we already did that with a "Next". + registration.getAdditionalJingleNodes().clear(); + + List jingleNodes + = iceConfigPanel.getAdditionalJingleNodes(); + + for (JingleNodeDescriptor descriptor : jingleNodes) + registration.addJingleNodes(descriptor); + nextPageIdentifier = SUMMARY_PAGE_IDENTIFIER; this.isCommitted = true; @@ -302,10 +321,8 @@ public void loadAccount(ProtocolProviderService protocolProvider) = StunServerDescriptor.loadDescriptor( accountProperties, ProtocolProviderFactory.STUN_PREFIX + i); - - // If we don't find a stun server with the given index, it means - // that there're no more servers left i nthe table so we've nothing + // that there're no more servers left in the table so we've nothing // more to do here. if (stunServer == null) break; @@ -313,6 +330,38 @@ public void loadAccount(ProtocolProviderService protocolProvider) iceConfigPanel.addStunServer(stunServer); } + String useJN = + accountProperties.get(ProtocolProviderFactory.IS_USE_JINGLE_NODES); + boolean isUseJN = Boolean.parseBoolean( + (useJN != null && useJN.length() != 0) ? useJN : "false"); + + iceConfigPanel.setUseJingleNodes(isUseJN); + + String useAutoDiscoverJN + = accountProperties.get( + ProtocolProviderFactory.AUTO_DISCOVER_JINGLE_NODES); + boolean isUseAutoDiscoverJN = Boolean.parseBoolean( + (useAutoDiscoverJN != null && + useAutoDiscoverJN.length() != 0) ? + useAutoDiscoverJN : "false"); + + iceConfigPanel.setAutoDiscoverJingleNodes(isUseAutoDiscoverJN); + + for (int i = 0; i < JingleNodeDescriptor.MAX_JN_RELAY_COUNT ; i ++) + { + JingleNodeDescriptor jn + = JingleNodeDescriptor.loadDescriptor( + accountProperties, JingleNodeDescriptor.JN_PREFIX + i); + + // If we don't find a stun server with the given index, it means + // that there're no more servers left in the table so we've nothing + // more to do here. + if (jn == null) + break; + + iceConfigPanel.addJingleNodes(jn); + } + this.isServerOverridden = accountID.getAccountPropertyBoolean( ProtocolProviderFactory.IS_SERVER_OVERRIDDEN, diff --git a/src/net/java/sip/communicator/plugin/jabberaccregwizz/IceConfigPanel.java b/src/net/java/sip/communicator/plugin/jabberaccregwizz/IceConfigPanel.java index d238766d7..7b8c64b65 100644 --- a/src/net/java/sip/communicator/plugin/jabberaccregwizz/IceConfigPanel.java +++ b/src/net/java/sip/communicator/plugin/jabberaccregwizz/IceConfigPanel.java @@ -54,13 +54,37 @@ public class IceConfigPanel /** * The table model for our additional stun servers table. */ - private final StunServerTableModel tableModel = new StunServerTableModel(); + private final ServerTableModel tableModel = new ServerTableModel(); /** * The stun server table. */ private final JTable table = new JTable(tableModel); + /** + * The check box allowing the user to choose to use JingleNodes. + */ + private final JCheckBox jnBox = new SIPCommCheckBox( + Resources.getString("plugin.jabberaccregwizz.USE_JINGLE_NODES")); + + /** + * The check box allowing the user to choose to automatically discover + * JingleNodes relays. + */ + private final JCheckBox jnAutoDiscoverBox = new SIPCommCheckBox( + Resources.getString("plugin.jabberaccregwizz.AUTO_DISCOVER_JN")); + + /** + * The table model for our additional stun servers table. + */ + private final ServerTableModel jnTableModel = + new ServerTableModel(); + + /** + * The JingleNodes server table. + */ + private final JTable jnTable = new JTable(jnTableModel); + /** * Creates an instance of IceConfigPanel. */ @@ -80,6 +104,9 @@ public IceConfigPanel() autoDiscoverBox.setSelected(true); defaultStunBox.setSelected(true); + //jnBox.setSelected(true); + //jnAutoDiscoverBox.setSelected(true); + JPanel checkBoxPanel = new TransparentPanel(new GridLayout(0, 1)); checkBoxPanel.add(iceBox); checkBoxPanel.add(autoDiscoverBox); @@ -88,6 +115,14 @@ public IceConfigPanel() add(checkBoxPanel); add(Box.createVerticalStrut(10)); add(createAdditionalServersComponent()); + + checkBoxPanel = new TransparentPanel(new GridLayout(0, 1)); + checkBoxPanel.add(jnBox); + checkBoxPanel.add(jnAutoDiscoverBox); + + add(checkBoxPanel); + add(Box.createVerticalStrut(10)); + add(createAdditionalJingleNodesComponent()); } /** @@ -106,7 +141,7 @@ private Component createAdditionalServersComponent() Resources.getString("plugin.jabberaccregwizz.SUPPORT_TURN")); table.setDefaultRenderer( StunServerDescriptor.class, - new StunServerCellRenderer()); + new ServerCellRenderer()); //Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(table); @@ -480,7 +515,7 @@ protected void close(boolean escaped) {} * A custom cell renderer used in the cell containing the * StunServer instance. */ - private static class StunServerCellRenderer + private static class ServerCellRenderer extends DefaultTableCellRenderer { /** @@ -564,6 +599,27 @@ public Component getTableCellRendererComponent( JTable table, : table.getBackground()); } } + else if(value instanceof JingleNodeDescriptor) + { + JingleNodeDescriptor jn = (JingleNodeDescriptor) value; + + this.setText(jn.getJID()); + + if (isSelected) + { + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } + else + { + super.setForeground((unselectedForeground != null) + ? unselectedForeground + : table.getForeground()); + super.setBackground((unselectedBackground != null) + ? unselectedBackground + : table.getBackground()); + } + } else return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); @@ -576,7 +632,7 @@ public Component getTableCellRendererComponent( JTable table, * A custom table model, with a non editable cells and a custom class column * objects. */ - private class StunServerTableModel + private class ServerTableModel extends DefaultTableModel { /** @@ -616,7 +672,7 @@ public boolean isCellEditable(int row, int column) * @return true if ICE should be used for this account, otherwise * returns false */ - boolean isUseIce() + protected boolean isUseIce() { return iceBox.isSelected(); } @@ -626,7 +682,7 @@ boolean isUseIce() * @param isUseIce true to indicate that ICE should be used for * this account, false - otherwise. */ - void setUseIce(boolean isUseIce) + protected void setUseIce(boolean isUseIce) { iceBox.setSelected(isUseIce); } @@ -636,7 +692,7 @@ void setUseIce(boolean isUseIce) * @return true if the stun server should be automatically * discovered, otherwise returns false. */ - boolean isAutoDiscoverStun() + protected boolean isAutoDiscoverStun() { return autoDiscoverBox.isSelected(); } @@ -646,7 +702,7 @@ boolean isAutoDiscoverStun() * @param isAutoDiscover true to indicate that stun server should * be auto-discovered, false - otherwise. */ - void setAutoDiscoverStun(boolean isAutoDiscover) + protected void setAutoDiscoverStun(boolean isAutoDiscover) { autoDiscoverBox.setSelected(isAutoDiscover); } @@ -656,7 +712,7 @@ void setAutoDiscoverStun(boolean isAutoDiscover) * @return true if the default stun server should be used, * otherwise returns false. */ - boolean isUseDefaultStunServer() + protected boolean isUseDefaultStunServer() { return defaultStunBox.isSelected(); } @@ -666,7 +722,7 @@ boolean isUseDefaultStunServer() * @param isDefaultStun true to indicate that the default stun * server should be used, false otherwise. */ - void setUseDefaultStunServer(boolean isDefaultStun) + protected void setUseDefaultStunServer(boolean isDefaultStun) { defaultStunBox.setSelected(isDefaultStun); } @@ -676,8 +732,8 @@ void setUseDefaultStunServer(boolean isDefaultStun) * * @return the list of additional stun servers entered by the user */ - @SuppressWarnings("unchecked")//getDataVector() is simply not parametrized - List getAdditionalStunServers() + @SuppressWarnings("unchecked")//getDataVector() is simply not parameterized + protected List getAdditionalStunServers() { LinkedList serversList = new LinkedList(); @@ -696,7 +752,7 @@ List getAdditionalStunServers() * servers. * @param stunServer the stun server to add */ - void addStunServer(StunServerDescriptor stunServer) + protected void addStunServer(StunServerDescriptor stunServer) { tableModel.addRow(new Object[]{stunServer, stunServer.isTurnSupported()}); @@ -707,7 +763,7 @@ void addStunServer(StunServerDescriptor stunServer) * * @param stunServer the stun server to modify */ - void modifyStunServer(StunServerDescriptor stunServer) + protected void modifyStunServer(StunServerDescriptor stunServer) { for (int i = 0; i < tableModel.getRowCount(); i++) { @@ -734,7 +790,7 @@ void modifyStunServer(StunServerDescriptor stunServer) * address and port already exists in the table, otherwise * returns null */ - StunServerDescriptor getStunServer(String address, int port) + protected StunServerDescriptor getStunServer(String address, int port) { for (int i = 0; i < tableModel.getRowCount(); i++) { @@ -791,4 +847,405 @@ public boolean verify(JComponent input) return NetworkUtils.isValidPortNumber(port); } } + + /** + * Creates the list of additional JingleNodes that are added by the user. + * + * @return the created component + */ + private Component createAdditionalJingleNodesComponent() + { + jnTable.setPreferredScrollableViewportSize(new Dimension(450, 60)); + + jnTableModel.addColumn( + Resources.getString("plugin.jabberaccregwizz.JID_ADDRESS")); + jnTableModel.addColumn( + Resources.getString("plugin.jabberaccregwizz.RELAY_SUPPORT")); + + jnTable.setDefaultRenderer(JingleNodeDescriptor.class, + new ServerCellRenderer()); + + //Create the scroll pane and add the table to it. + JScrollPane scrollPane = new JScrollPane(jnTable); + + JButton addButton + = new JButton(Resources.getString("service.gui.ADD")); + addButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + JNConfigDialog jnDialog = new JNConfigDialog(false); + + jnDialog.setVisible(true); + } + }); + + JButton editButton + = new JButton(Resources.getString("service.gui.EDIT")); + editButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + if(jnTable.getSelectedRow() < 0) + return; + + JingleNodeDescriptor jn + = (JingleNodeDescriptor) jnTableModel.getValueAt( + jnTable.getSelectedRow(), 0); + + if (jn != null) + { + JNConfigDialog dialog = new JNConfigDialog( + jn.getJID(), jn.isRelaySupported()); + + dialog.setVisible(true); + } + } + }); + + JButton deleteButton + = new JButton(Resources.getString("service.gui.DELETE")); + deleteButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + jnTableModel.removeRow(jnTable.getSelectedRow()); + } + }); + + TransparentPanel buttonsPanel + = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); + + buttonsPanel.add(addButton); + buttonsPanel.add(editButton); + buttonsPanel.add(deleteButton); + + TransparentPanel mainPanel = new TransparentPanel(new BorderLayout()); + mainPanel.setBorder(BorderFactory.createTitledBorder( + Resources.getString( + "plugin.jabberaccregwizz.ADDITIONAL_JINGLE_NODES"))); + mainPanel.add(scrollPane); + mainPanel.add(buttonsPanel, BorderLayout.SOUTH); + + return mainPanel; + } + + /** + * The JingleNodes configuration window. + */ + private class JNConfigDialog extends SIPCommDialog + { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * The main panel + */ + private final JPanel mainPanel + = new TransparentPanel(new BorderLayout()); + + /** + * The address of the stun server. + */ + private final JTextField addressField = new JTextField(); + + /** + * The check box where user would indicate whether a STUN server is also + * a TURN server. + */ + private final JCheckBox supportRelayCheckBox = new JCheckBox( + Resources.getString("plugin.jabberaccregwizz.RELAY_SUPPORT")); + + /** + * The pane where we show errors. + */ + private JEditorPane errorMessagePane; + + /** + * If the dialog is open via "edit" button. + */ + private final boolean isEditMode; + + /** + * Creates a new JNConfigDialog with filled in values. + * + * @param address the IP or FQDN of the server + * @param isRelaySupport a boolean indicating whether the node + * supports relay + */ + public JNConfigDialog(String address, boolean isRelaySupport) + { + this(true); + + addressField.setText(address); + supportRelayCheckBox.setSelected(isRelaySupport); + } + + /** + * Creates an empty dialog. + * + * @param editMode true if the dialog is in "edit" state, false means + * "add" state + */ + public JNConfigDialog(boolean editMode) + { + super(false); + + this.isEditMode = editMode; + + setTitle(Resources.getString( + "plugin.jabberaccregwizz.ADD_JINGLE_NODE")); + + JLabel addressLabel = new JLabel( + Resources.getString("plugin.jabberaccregwizz.JID_ADDRESS")); + + TransparentPanel labelsPanel + = new TransparentPanel(new GridLayout(0, 1)); + + labelsPanel.add(addressLabel); + labelsPanel.add(new JLabel()); + + TransparentPanel valuesPanel + = new TransparentPanel(new GridLayout(0, 1)); + + valuesPanel.add(addressField); + valuesPanel.add(supportRelayCheckBox); + + JButton addButton + = new JButton(Resources.getString(isEditMode ? + "service.gui.EDIT" : "service.gui.ADD")); + JButton cancelButton + = new JButton(Resources.getString("service.gui.CANCEL")); + + addButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + String address = addressField.getText(); + JingleNodeDescriptor jnServer = null; + + String errorMessage = null; + if (address == null || address.length() <= 0) + errorMessage = Resources.getString( + "plugin.jabberaccregwizz.NO_STUN_ADDRESS"); + + jnServer = getJingleNodes(address); + + if(jnServer != null && !isEditMode) + { + errorMessage = Resources.getString( + "plugin.jabberaccregwizz.STUN_ALREADY_EXIST"); + } + + if (errorMessage != null) + { + loadErrorMessage(errorMessage); + return; + } + + if(!isEditMode) + { + jnServer = new JingleNodeDescriptor( + address, supportRelayCheckBox.isSelected()); + + addJingleNodes(jnServer); + } + else + { + /* edit an existing Jingle Node */ + jnServer.setAddress(address); + + jnServer.setRelay(supportRelayCheckBox.isSelected()); + modifyJingleNodes(jnServer); + } + dispose(); + } + }); + + cancelButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + dispose(); + } + }); + + TransparentPanel buttonsPanel + = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonsPanel.add(addButton); + buttonsPanel.add(cancelButton); + + mainPanel.add(labelsPanel, BorderLayout.WEST); + mainPanel.add(valuesPanel, BorderLayout.CENTER); + mainPanel.add(buttonsPanel, BorderLayout.SOUTH); + + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, + 20)); + getContentPane().add(mainPanel, BorderLayout.NORTH); + pack(); + } + + /** + * Loads the given error message in the current dialog, by re-validating + * the content. + * + * @param errorMessage The error message to load. + */ + private void loadErrorMessage(String errorMessage) + { + if (errorMessagePane == null) + { + errorMessagePane = new JEditorPane(); + + errorMessagePane.setOpaque(false); + errorMessagePane.setForeground(Color.RED); + + mainPanel.add(errorMessagePane, BorderLayout.NORTH); + } + + errorMessagePane.setText(errorMessage); + mainPanel.revalidate(); + mainPanel.repaint(); + + this.pack(); + + //WORKAROUND: there's something wrong happening in this pack and + //components get cluttered, partially hiding the password text field. + //I am under the impression that this has something to do with the + //message pane preferred size being ignored (or being 0) which is + //why I am adding it's height to the dialog. It's quite ugly so + //please fix if you have something better in mind. + this.setSize(getWidth(), getHeight() + + errorMessagePane.getHeight()); + } + + /** + * Dummy implementation that we are not using. + * + * @param escaped unused + */ + @Override + protected void close(boolean escaped) {} + } + + /** + * Indicates if Jingle Nodes should be used for this account. + * + * @return true if Jingle Nodes should be used for this account, + * otherwise returns false + */ + protected boolean isUseJingleNodes() + { + return jnBox.isSelected(); + } + + /** + * Sets the useJingleNodes property. + * + * @param isUseJN true to indicate that Jingle Nodes should be + * used for this account, false - otherwise. + */ + protected void setUseJingleNodes(boolean isUseJN) + { + jnBox.setSelected(isUseJN); + } + + /** + * Indicates if the Jingle Nodes relays should be automatically discovered. + * + * @return true if the Jingle Nodes relays should be automatically + * discovered, otherwise returns false. + */ + protected boolean isAutoDiscoverJingleNodes() + { + return jnAutoDiscoverBox.isSelected(); + } + + /** + * Sets the autoDiscoverJingleNodes property. + * + * @param isAutoDiscover true to indicate that Jingle Nodes relays + * should be auto-discovered, false - otherwise. + */ + protected void setAutoDiscoverJingleNodes(boolean isAutoDiscover) + { + jnAutoDiscoverBox.setSelected(isAutoDiscover); + } + + /** + * Returns the list of additional Jingle Nodes entered by the user. + * + * @return the list of additional Jingle Nodes entered by the user + */ + @SuppressWarnings("unchecked")//getDataVector() is simply not parameterized + protected List getAdditionalJingleNodes() + { + LinkedList serversList + = new LinkedList(); + + Vector> serverRows + = jnTableModel.getDataVector(); + + for(Vector row : serverRows) + serversList.add(row.elementAt(0)); + + return serversList; + } + + /** + * Indicates if a JingleNodes with the given address already exists + * in the additional stun servers table. + * + * @param address the JingleNodes address to check + * + * @return JingleNodesDescriptor if a Jingle Node with the given + * address already exists in the table, otherwise returns + * null + */ + protected JingleNodeDescriptor getJingleNodes(String address) + { + for (int i = 0; i < jnTableModel.getRowCount(); i++) + { + JingleNodeDescriptor jn + = (JingleNodeDescriptor) jnTableModel.getValueAt(i, 0); + + if (jn.getJID().equalsIgnoreCase(address)) + return jn; + } + return null; + } + + /** + * Adds the given jingleNode to the list of additional JingleNodes + * + * @param jingleNode the Jingle Node server to add + */ + protected void addJingleNodes(JingleNodeDescriptor jingleNode) + { + jnTableModel.addRow(new Object[]{jingleNode, + jingleNode.isRelaySupported()}); + } + + /** + * Modify the given jingleNode from the list of Jingle Nodes. + * + * @param jingleNode the Jingle Node to modify + */ + protected void modifyJingleNodes(JingleNodeDescriptor jingleNode) + { + for (int i = 0; i < jnTableModel.getRowCount(); i++) + { + JingleNodeDescriptor node + = (JingleNodeDescriptor) jnTableModel.getValueAt(i, 0); + + if(jingleNode == node) + { + jnTableModel.setValueAt(jingleNode, i, 0); + jnTableModel.setValueAt(jingleNode.isRelaySupported(), i, 1); + return; + } + } + } } diff --git a/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistration.java b/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistration.java index 33185b9bd..dcb37ce65 100755 --- a/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistration.java +++ b/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistration.java @@ -104,6 +104,22 @@ public class JabberAccountRegistration private List additionalStunServers = new ArrayList(); + /** + * Indicates if JingleNodes relays should be used. + */ + private boolean isUseJingleNodes = false; + + /** + * Indicates if JingleNodes relay server should be automatically discovered. + */ + private boolean isAutoDiscoverJingleNodes = false; + + /** + * The list of additional JingleNodes (tracker or relay) entered by user. + */ + private List additionalJingleNodes + = new ArrayList(); + /** * Returns the password of the jabber registration account. * @return the password of the jabber registration account. @@ -363,4 +379,82 @@ public List getAdditionalStunServers() { return additionalStunServers; } + + /** + * Sets the autoDiscoverJingleNodes property. + * + * @param isAutoDiscover true to indicate that relay server should + * be auto-discovered, false - otherwise. + */ + public void setAutoDiscoverJingleNodes(boolean isAutoDiscover) + { + this.isAutoDiscoverJingleNodes = isAutoDiscover; + } + + /** + * Indicates if the JingleNodes relay server should be automatically + * discovered. + * + * @return true if the relay server should be automatically + * discovered, otherwise returns false. + */ + public boolean isAutoDiscoverJingleNodes() + { + return isAutoDiscoverJingleNodes; + } + + /** + * Sets the useJingleNodes/tt> property. + * + * @param isUseJingleNodes true to indicate that Jingle Nodes + * should be used for this account, false - otherwise. + */ + public void setUseJingleNodes(boolean isUseJingleNodes) + { + this.isUseJingleNodes = isUseJingleNodes; + } + + /** + * Sets the useJingleNodes property. + * + * @param isUseJingleNodes true to indicate that JingleNodes relays + * should be used for this account, false - otherwise. + */ + public void isUseJingleNodes(boolean isUseJingleNodes) + { + this.isUseJingleNodes = isUseJingleNodes; + } + + /** + * Indicates if JingleNodes relay should be used. + * + * @return true if JingleNodes should be used, false + * otherwise + */ + public boolean isUseJingleNodes() + { + return isUseJingleNodes; + } + + /** + * Adds the given node to the list of additional JingleNodes. + * + * @param node the node to add + */ + public void addJingleNodes(JingleNodeDescriptor node) + { + additionalJingleNodes.add(node); + } + + /** + * Returns the List of all additional stun servers entered by the + * user. The list is guaranteed not to be null. + * + * @return the List of all additional stun servers entered by the + * user. + */ + public List getAdditionalJingleNodes() + { + return additionalJingleNodes; + } } diff --git a/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistrationWizard.java b/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistrationWizard.java index 9eaf734e1..b8abbee84 100644 --- a/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistrationWizard.java +++ b/src/net/java/sip/communicator/plugin/jabberaccregwizz/JabberAccountRegistrationWizard.java @@ -26,11 +26,20 @@ public class JabberAccountRegistrationWizard implements AccountRegistrationWizard { + /** + * The logger. + */ private static final Logger logger = Logger.getLogger(JabberAccountRegistrationWizard.class); + /** + * Account suffix for Google service. + */ private static final String GOOGLE_USER_SUFFIX = "gmail.com"; + /** + * XMPP server for Google service. + */ private static final String GOOGLE_CONNECT_SRV = "talk.google.com"; private FirstWizardPage firstWizardPage; @@ -40,8 +49,14 @@ public class JabberAccountRegistrationWizard private final WizardContainer wizardContainer; + /** + * The ProtocolProviderService of this account. + */ private ProtocolProviderService protocolProvider; + /** + * If the account has been modified. + */ private boolean isModification; /** @@ -243,6 +258,7 @@ public ProtocolProviderService installAccount( { serverName = getServerFromUserName(userName); } + accountProperties.put(ProtocolProviderFactory.SERVER_ADDRESS, serverName); @@ -277,6 +293,25 @@ public ProtocolProviderService installAccount( ProtocolProviderFactory.STUN_PREFIX + serverIndex); } + accountProperties.put(ProtocolProviderFactory.IS_USE_JINGLE_NODES, + String.valueOf(registration.isUseJingleNodes())); + + accountProperties.put( + ProtocolProviderFactory.AUTO_DISCOVER_JINGLE_NODES, + String.valueOf(registration.isAutoDiscoverJingleNodes())); + + serverIndex = -1; + List jnRelays + = registration.getAdditionalJingleNodes(); + + for(JingleNodeDescriptor jnRelay : jnRelays) + { + serverIndex ++; + + jnRelay.storeDescriptor(accountProperties, + JingleNodeDescriptor.JN_PREFIX + serverIndex); + } + if (isModification) { providerFactory.modifyAccount( protocolProvider, diff --git a/src/net/java/sip/communicator/service/protocol/JingleNodeDescriptor.java b/src/net/java/sip/communicator/service/protocol/JingleNodeDescriptor.java new file mode 100644 index 000000000..7e7d4c51d --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/JingleNodeDescriptor.java @@ -0,0 +1,185 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol; + +import java.util.*; + +/** + * A JingleNodesDescriptor stores information necessary to create a + * JingleNodes tracker or relay candidate harvester that we could use with + * ICE4J. Descriptors are normally initialized by protocol wizards. They are + * then used to convert the data into a {@link String} form suitable for storage + * in an accounts properties Map. + * + * @author Yana Stamcheva + * @author Emil Ivov + * @author Sebastien Vincent + */ +public class JingleNodeDescriptor +{ + /** + * JingleNodes prefix to store configuration. + */ + public static final String JN_PREFIX = "JINGLENODES"; + + /** + * JingleNodes prefix to store server address in configuration. + */ + public static final String JN_ADDRESS = "ADDRESS"; + + /** + * JingleNodes prefix to store the relay capabilities in configuration. + */ + public static final String JN_IS_RELAY_SUPPORTED = "IS_RELAY_SUPPORTED"; + + /** + * The maximum number of stun servers that we would allow. + */ + public static final int MAX_JN_RELAY_COUNT = 100; + + /** + * The address of the JingleNodes (JID). + */ + private String address; + + /** + * If the relay is supported by this JingleNodes. + */ + private boolean relaySupported; + + /** + * Creates an instance of JingleNodes by specifying all + * parameters. + * + * @param address address of the JingleNodes + * @param relaySupported if the JingleNodes supports relay + */ + public JingleNodeDescriptor(String address, + boolean relaySupported) + { + this.address = address; + this.relaySupported = relaySupported; + } + + /** + * Returns the address of the JingleNodes + * + * @return the address of the JingleNodes + */ + public String getJID() + { + return address; + } + + /** + * Sets the address of the JingleNodes. + * + * @param address the JID of the JingleNodes + */ + public void setAddress(String address) + { + this.address = address; + } + + /** + * Returns if the JID has relay support. + * + * @return true if relay is supported, false otherwise + */ + public boolean isRelaySupported() + { + return relaySupported; + } + + /** + * Sets the relay support corresponding to this JID. + * + * @param relaySupported relay value to set + */ + public void setRelay(boolean relaySupported) + { + this.relaySupported = relaySupported; + } + + /** + * Stores this descriptor into the specified {@link Map}.The method is meant + * for use with account property maps. It also allows prepending an account + * prefix to all property names so that multiple descriptors can be stored + * in a single {@link Map}. + * + * @param props the account properties {@link Map} that we'd like to store + * this descriptor in. + * @param namePrefix the prefix that we should prepend to every property + * name. + */ + public void storeDescriptor(Map props, String namePrefix) + { + if(namePrefix == null) + namePrefix = ""; + + props.put(namePrefix + JN_ADDRESS, getJID()); + + + props.put(namePrefix + JN_IS_RELAY_SUPPORTED, + Boolean.toString(isRelaySupported())); + } + + /** + * Loads this descriptor from the specified {@link Map}.The method is meant + * for use with account property maps. It also allows prepending an account + * prefix to all property names so that multiple descriptors can be read + * in a single {@link Map}. + * + * @param props the account properties {@link Map} that we'd like to load + * this descriptor from. + * @param namePrefix the prefix that we should prepend to every property + * name. + * + * @return the newly created descriptor or null if no descriptor was found. + */ + public static JingleNodeDescriptor loadDescriptor( + Map props, + String namePrefix) + { + if(namePrefix == null) + namePrefix = ""; + + String relayAddress = props.get(namePrefix + JN_ADDRESS); + + if (relayAddress == null) + return null; + + String relayStr = props.get(namePrefix + JN_IS_RELAY_SUPPORTED); + + boolean relay = false; + + try + { + relay = Boolean.parseBoolean(relayStr); + } + catch(Throwable t) + { + } + + JingleNodeDescriptor relayServer = + new JingleNodeDescriptor(relayAddress, + relay); + + return relayServer; + } + + /** + * Returns a String representation of this descriptor + * + * @return a String representation of this descriptor. + */ + public String toString() + { + return "JingleNodesDesc: " + getJID() + " relay:" + + isRelaySupported(); + } +} diff --git a/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java b/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java index 33c2004b1..6402a3f3e 100644 --- a/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java +++ b/src/net/java/sip/communicator/service/protocol/ProtocolProviderFactory.java @@ -160,7 +160,6 @@ public abstract class ProtocolProviderFactory */ public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT"; - /** * The name of the property under which we store the user preference for a * transport protocol to use (i.e. tcp or udp). @@ -314,6 +313,17 @@ public abstract class ProtocolProviderFactory */ public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED"; + /** + * Indicates if JingleNodes should be used with ICE. + */ + public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED"; + + /** + * Indicates if JingleNodes should be used with ICE. + */ + public static final String AUTO_DISCOVER_JINGLE_NODES + = "AUTO_DISCOVER_JINGLE_NODES"; + /** * Address used to reach voicemail box, by services able to * subscribe for voicemail new messages notifications. diff --git a/src/net/java/sip/communicator/service/protocol/StunServerDescriptor.java b/src/net/java/sip/communicator/service/protocol/StunServerDescriptor.java index 5e5df329d..6a43bd23e 100644 --- a/src/net/java/sip/communicator/service/protocol/StunServerDescriptor.java +++ b/src/net/java/sip/communicator/service/protocol/StunServerDescriptor.java @@ -6,7 +6,6 @@ */ package net.java.sip.communicator.service.protocol; -import java.io.*; import java.util.*; import net.java.sip.communicator.util.*;