001/**
002 *
003 * Copyright 2003-2005 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smackx.jingleold.nat;
019
020import java.io.IOException;
021import java.net.InetAddress;
022import java.net.NetworkInterface;
023import java.net.SocketException;
024import java.util.Enumeration;
025import java.util.logging.Level;
026import java.util.logging.Logger;
027
028import org.jivesoftware.smack.SmackException;
029import org.jivesoftware.smack.SmackException.NoResponseException;
030import org.jivesoftware.smack.SmackException.NotConnectedException;
031import org.jivesoftware.smack.StanzaCollector;
032import org.jivesoftware.smack.XMPPConnection;
033import org.jivesoftware.smack.XMPPException.XMPPErrorException;
034import org.jivesoftware.smack.packet.IQ;
035import org.jivesoftware.smack.provider.IQProvider;
036import org.jivesoftware.smack.provider.ProviderManager;
037
038import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
039import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
040
041import org.xmlpull.v1.XmlPullParser;
042import org.xmlpull.v1.XmlPullParserException;
043
044/**
045 * RTPBridge IQ Stanza used to request and retrieve a RTPBridge Candidates that can be used for a Jingle Media Transmission between two parties that are behind NAT.
046 * This Jingle Bridge has all the needed information to establish a full UDP Channel (Send and Receive) between two parties.
047 * <i>This transport method should be used only if other transport methods are not allowed. Or if you want a more reliable transport.</i>
048 *
049 * High Level Usage Example:
050 *
051 * RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, sessionID);
052 *
053 * @author Thiago Camargo
054 */
055public class RTPBridge extends IQ {
056
057    private static final Logger LOGGER = Logger.getLogger(RTPBridge.class.getName());
058
059    private String sid;
060    private String pass;
061    private String ip;
062    private String name;
063    private int portA = -1;
064    private int portB = -1;
065    private String hostA;
066    private String hostB;
067    private BridgeAction bridgeAction = BridgeAction.create;
068
069    private enum BridgeAction {
070
071        create, change, publicip
072    }
073
074    /**
075     * Element name of the stanza extension.
076     */
077    public static final String NAME = "rtpbridge";
078
079    /**
080     * Element name of the stanza extension.
081     */
082    public static final String ELEMENT_NAME = "rtpbridge";
083
084    /**
085     * Namespace of the stanza extension.
086     */
087    public static final String NAMESPACE = "http://www.jivesoftware.com/protocol/rtpbridge";
088
089    static {
090        ProviderManager.addIQProvider(NAME, NAMESPACE, new Provider());
091    }
092
093    /**
094     * Creates a RTPBridge Instance with defined Session ID.
095     *
096     * @param sid
097     */
098    public RTPBridge(String sid) {
099        this();
100        this.sid = sid;
101    }
102
103    /**
104     * Creates a RTPBridge Instance with defined Session ID.
105     *
106     * @param action
107     */
108    public RTPBridge(BridgeAction action) {
109        this();
110        this.bridgeAction = action;
111    }
112
113    /**
114     * Creates a RTPBridge Instance with defined Session ID.
115     *
116     * @param sid
117     * @param bridgeAction
118     */
119    public RTPBridge(String sid, BridgeAction bridgeAction) {
120        this();
121        this.sid = sid;
122        this.bridgeAction = bridgeAction;
123    }
124
125    /**
126     * Creates a RTPBridge Stanza without Session ID.
127     */
128    public RTPBridge() {
129        super(ELEMENT_NAME, NAMESPACE);
130    }
131
132    /**
133     * Get the attributes string.
134     */
135    public String getAttributes() {
136        StringBuilder str = new StringBuilder();
137
138        if (getSid() != null)
139            str.append(" sid='").append(getSid()).append('\'');
140
141        if (getPass() != null)
142            str.append(" pass='").append(getPass()).append('\'');
143
144        if (getPortA() != -1)
145            str.append(" porta='").append(getPortA()).append('\'');
146
147        if (getPortB() != -1)
148            str.append(" portb='").append(getPortB()).append('\'');
149
150        if (getHostA() != null)
151            str.append(" hosta='").append(getHostA()).append('\'');
152
153        if (getHostB() != null)
154            str.append(" hostb='").append(getHostB()).append('\'');
155
156        return str.toString();
157    }
158
159    /**
160     * Get the Session ID of the Stanza (usually same as Jingle Session ID).
161     *
162     * @return the session ID
163     */
164    public String getSid() {
165        return sid;
166    }
167
168    /**
169     * Set the Session ID of the Stanza (usually same as Jingle Session ID).
170     *
171     * @param sid
172     */
173    public void setSid(String sid) {
174        this.sid = sid;
175    }
176
177    /**
178     * Get the Host A IP Address.
179     *
180     * @return the Host A IP Address
181     */
182    public String getHostA() {
183        return hostA;
184    }
185
186    /**
187     * Set the Host A IP Address.
188     *
189     * @param hostA
190     */
191    public void setHostA(String hostA) {
192        this.hostA = hostA;
193    }
194
195    /**
196     * Get the Host B IP Address.
197     *
198     * @return the Host B IP Address
199     */
200    public String getHostB() {
201        return hostB;
202    }
203
204    /**
205     * Set the Host B IP Address.
206     *
207     * @param hostB
208     */
209    public void setHostB(String hostB) {
210        this.hostB = hostB;
211    }
212
213    /**
214     * Get Side A receive port.
215     *
216     * @return the side A receive port
217     */
218    public int getPortA() {
219        return portA;
220    }
221
222    /**
223     * Set Side A receive port.
224     *
225     * @param portA
226     */
227    public void setPortA(int portA) {
228        this.portA = portA;
229    }
230
231    /**
232     * Get Side B receive port.
233     *
234     * @return the side B receive port
235     */
236    public int getPortB() {
237        return portB;
238    }
239
240    /**
241     * Set Side B receive port.
242     *
243     * @param portB
244     */
245    public void setPortB(int portB) {
246        this.portB = portB;
247    }
248
249    /**
250     * Get the RTP Bridge IP.
251     *
252     * @return the RTP Bridge IP
253     */
254    public String getIp() {
255        return ip;
256    }
257
258    /**
259     * Set the RTP Bridge IP.
260     *
261     * @param ip
262     */
263    public void setIp(String ip) {
264        this.ip = ip;
265    }
266
267    /**
268     * Get the RTP Agent Pass.
269     *
270     * @return the RTP Agent Pass
271     */
272    public String getPass() {
273        return pass;
274    }
275
276    /**
277     * Set the RTP Agent Pass.
278     *
279     * @param pass
280     */
281    public void setPass(String pass) {
282        this.pass = pass;
283    }
284
285    /**
286     * Get the name of the Candidate.
287     *
288     * @return the name of the Candidate
289     */
290    public String getName() {
291        return name;
292    }
293
294    /**
295     * Set the name of the Candidate.
296     *
297     * @param name
298     */
299    public void setName(String name) {
300        this.name = name;
301    }
302
303    /**
304     * Get the Child Element XML of the Packet
305     *
306     * @return the Child Element XML of the Packet
307     */
308    @Override
309    protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder str) {
310        str.attribute("sid", sid);
311        str.rightAngleBracket();
312
313        if (bridgeAction.equals(BridgeAction.create))
314            str.append("<candidate/>");
315        else if (bridgeAction.equals(BridgeAction.change))
316            str.append("<relay ").append(getAttributes()).append(" />");
317        else
318            str.append("<publicip ").append(getAttributes()).append(" />");
319
320        return str;
321    }
322
323    /**
324     * IQProvider for RTP Bridge packets.
325     * Parse receive RTPBridge stanza to a RTPBridge instance
326     *
327     * @author Thiago Rocha
328     */
329    public static class Provider extends IQProvider<RTPBridge> {
330
331        @Override
332        public RTPBridge parse(XmlPullParser parser, int initialDepth)
333                        throws SmackException, XmlPullParserException,
334                        IOException {
335
336            boolean done = false;
337
338            int eventType;
339            String elementName;
340
341            if (!parser.getNamespace().equals(RTPBridge.NAMESPACE))
342                throw new SmackException("Not a RTP Bridge packet");
343
344            RTPBridge iq = new RTPBridge();
345
346            for (int i = 0; i < parser.getAttributeCount(); i++) {
347                if (parser.getAttributeName(i).equals("sid"))
348                    iq.setSid(parser.getAttributeValue(i));
349            }
350
351            // Start processing sub-elements
352            while (!done) {
353                eventType = parser.next();
354                elementName = parser.getName();
355
356                if (eventType == XmlPullParser.START_TAG) {
357                    if (elementName.equals("candidate")) {
358                        for (int i = 0; i < parser.getAttributeCount(); i++) {
359                            if (parser.getAttributeName(i).equals("ip"))
360                                iq.setIp(parser.getAttributeValue(i));
361                            else if (parser.getAttributeName(i).equals("pass"))
362                                iq.setPass(parser.getAttributeValue(i));
363                            else if (parser.getAttributeName(i).equals("name"))
364                                iq.setName(parser.getAttributeValue(i));
365                            else if (parser.getAttributeName(i).equals("porta"))
366                                iq.setPortA(Integer.parseInt(parser.getAttributeValue(i)));
367                            else if (parser.getAttributeName(i).equals("portb"))
368                                iq.setPortB(Integer.parseInt(parser.getAttributeValue(i)));
369                        }
370                    }
371                    else if (elementName.equals("publicip")) {
372                        for (int i = 0; i < parser.getAttributeCount(); i++) {
373                            if (parser.getAttributeName(i).equals("ip"))
374                                iq.setIp(parser.getAttributeValue(i));
375                        }
376                    }
377                }
378                else if (eventType == XmlPullParser.END_TAG) {
379                    if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) {
380                        done = true;
381                    }
382                }
383            }
384            return iq;
385        }
386    }
387
388    /**
389     * Get a new RTPBridge Candidate from the server.
390     * If a error occurs or the server don't support RTPBridge Service, null is returned.
391     *
392     * @param connection
393     * @param sessionID
394     * @return the new RTPBridge
395     * @throws NotConnectedException
396     * @throws InterruptedException
397     */
398    @SuppressWarnings("deprecation")
399    public static RTPBridge getRTPBridge(XMPPConnection connection, String sessionID) throws NotConnectedException, InterruptedException {
400
401        if (!connection.isConnected()) {
402            return null;
403        }
404
405        RTPBridge rtpPacket = new RTPBridge(sessionID);
406        rtpPacket.setTo(RTPBridge.NAME + "." + connection.getXMPPServiceDomain());
407
408        StanzaCollector collector = connection.createStanzaCollectorAndSend(rtpPacket);
409
410        RTPBridge response = collector.nextResult();
411
412        // Cancel the collector.
413        collector.cancel();
414
415        return response;
416    }
417
418    /**
419     * Check if the server support RTPBridge Service.
420     *
421     * @param connection
422     * @return true if the server supports the RTPBridge service
423     * @throws XMPPErrorException
424     * @throws NoResponseException
425     * @throws NotConnectedException
426     * @throws InterruptedException
427     */
428    public static boolean serviceAvailable(XMPPConnection connection) throws NoResponseException,
429                    XMPPErrorException, NotConnectedException, InterruptedException {
430
431        if (!connection.isConnected()) {
432            return false;
433        }
434
435        LOGGER.fine("Service listing");
436
437        ServiceDiscoveryManager disco = ServiceDiscoveryManager
438                .getInstanceFor(connection);
439//            DiscoverItems items = disco.discoverItems(connection.getXMPPServiceDomain());
440//            Iterator iter = items.getItems();
441//            while (iter.hasNext()) {
442//                DiscoverItems.Item item = (DiscoverItems.Item) iter.next();
443//                if (item.getEntityID().startsWith("rtpbridge.")) {
444//                    return true;
445//                }
446//            }
447
448        DiscoverInfo discoInfo = disco.discoverInfo(connection.getXMPPServiceDomain());
449        for (DiscoverInfo.Identity identity : discoInfo.getIdentities()) {
450            if ((identity.getName() != null) && (identity.getName().startsWith("rtpbridge"))) {
451                return true;
452            }
453        }
454
455        return false;
456    }
457
458    /**
459     * Check if the server support RTPBridge Service.
460     *
461     * @param connection
462     * @return the RTPBridge
463     * @throws NotConnectedException
464     * @throws InterruptedException
465     */
466    @SuppressWarnings("deprecation")
467    public static RTPBridge relaySession(XMPPConnection connection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) throws NotConnectedException, InterruptedException {
468
469        if (!connection.isConnected()) {
470            return null;
471        }
472
473        RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change);
474        rtpPacket.setTo(RTPBridge.NAME + "." + connection.getXMPPServiceDomain());
475        rtpPacket.setType(Type.set);
476
477        rtpPacket.setPass(pass);
478        rtpPacket.setPortA(localCandidate.getPort());
479        rtpPacket.setPortB(proxyCandidate.getPort());
480        rtpPacket.setHostA(localCandidate.getIp());
481        rtpPacket.setHostB(proxyCandidate.getIp());
482
483        // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
484
485        StanzaCollector collector = connection.createStanzaCollectorAndSend(rtpPacket);
486
487        RTPBridge response = collector.nextResult();
488
489        // Cancel the collector.
490        collector.cancel();
491
492        return response;
493    }
494
495    /**
496     * Get Public Address from the Server.
497     *
498     * @param xmppConnection
499     * @return public IP String or null if not found
500     * @throws NotConnectedException
501     * @throws InterruptedException
502     */
503    @SuppressWarnings("deprecation")
504    public static String getPublicIP(XMPPConnection xmppConnection) throws NotConnectedException, InterruptedException {
505
506        if (!xmppConnection.isConnected()) {
507            return null;
508        }
509
510        RTPBridge rtpPacket = new RTPBridge(RTPBridge.BridgeAction.publicip);
511        rtpPacket.setTo(RTPBridge.NAME + "." + xmppConnection.getXMPPServiceDomain());
512        rtpPacket.setType(Type.set);
513
514        // LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
515
516        StanzaCollector collector = xmppConnection.createStanzaCollectorAndSend(rtpPacket);
517
518        RTPBridge response = collector.nextResult();
519
520        // Cancel the collector.
521        collector.cancel();
522
523        if (response == null) return null;
524
525        if (response.getIp() == null || response.getIp().equals("")) return null;
526
527        Enumeration<NetworkInterface> ifaces = null;
528        try {
529            ifaces = NetworkInterface.getNetworkInterfaces();
530        }
531        catch (SocketException e) {
532            LOGGER.log(Level.WARNING, "exception", e);
533        }
534        while (ifaces != null && ifaces.hasMoreElements()) {
535
536            NetworkInterface iface = ifaces.nextElement();
537            Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
538
539            while (iaddresses.hasMoreElements()) {
540                InetAddress iaddress = iaddresses.nextElement();
541                if (!iaddress.isLoopbackAddress())
542                    if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0)
543                        return null;
544
545            }
546        }
547
548        return response.getIp();
549    }
550
551}