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