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