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