/** * Returns default <tt>WebRtcDataStream</tt> if it's ready or <tt>null</tt> otherwise. * * @return <tt>WebRtcDataStream</tt> if it's ready or <tt>null</tt> otherwise. * @throws IOException */ public WebRtcDataStream getDefaultDataStream() throws IOException { WebRtcDataStream def; synchronized (this) { if (sctpSocket == null) { def = null; } else { // Channel that runs on sid 0 def = channels.get(0); if (def == null) { def = openChannel(0, 0, 0, 0, "default"); } // Pawel Domas: Must be acknowledged before use /* * XXX Lyubomir Marinov: We're always sending ordered. According * to "WebRTC Data Channel Establishment Protocol", we can start * sending messages containing user data after the * DATA_CHANNEL_OPEN message has been sent without waiting for * the reception of the corresponding DATA_CHANNEL_ACK message. */ // if (!def.isAcknowledged()) // def = null; } } return def; }
/** * Remove records telling what entity caps node a contact has. * * @param contact the contact */ public void removeContactCapsNode(Contact contact) { Caps caps = null; String lastRemovedJid = null; Iterator<String> iter = userCaps.keySet().iterator(); while (iter.hasNext()) { String jid = iter.next(); if (StringUtils.parseBareAddress(jid).equals(contact.getAddress())) { caps = userCaps.get(jid); lastRemovedJid = jid; iter.remove(); } } // fire only for the last one, at the end the event out // of the protocol will be one and for the contact if (caps != null) { UserCapsNodeListener[] listeners; synchronized (userCapsNodeListeners) { listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS); } if (listeners.length != 0) { String nodeVer = caps.getNodeVer(); for (UserCapsNodeListener listener : listeners) listener.userCapsNodeRemoved(lastRemovedJid, nodeVer, false); } } }
/** * Add a record telling what entity caps node a user has. * * @param user the user (Full JID) * @param node the node (of the caps packet extension) * @param hash the hashing algorithm used to calculate <tt>ver</tt> * @param ver the version (of the caps packet extension) * @param ext the ext (of the caps packet extension) * @param online indicates if the user is online */ private void addUserCapsNode( String user, String node, String hash, String ver, String ext, boolean online) { if ((user != null) && (node != null) && (hash != null) && (ver != null)) { Caps caps = userCaps.get(user); if ((caps == null) || !caps.node.equals(node) || !caps.hash.equals(hash) || !caps.ver.equals(ver)) { caps = new Caps(node, hash, ver, ext); userCaps.put(user, caps); } else return; // Fire userCapsNodeAdded. UserCapsNodeListener[] listeners; synchronized (userCapsNodeListeners) { listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS); } if (listeners.length != 0) { String nodeVer = caps.getNodeVer(); for (UserCapsNodeListener listener : listeners) listener.userCapsNodeAdded(user, nodeVer, online); } } }
/** * Returns {@link JitsiMeetConference} for given MUC {@code roomName} or {@code null} if no * conference has been allocated yet. * * @param roomName the name of MUC room for which we want get the {@code JitsiMeetConference} * instance. * @return the {@code JitsiMeetConference} for the specified {@code roomName} or {@code null} if * no conference has been allocated yet */ public JitsiMeetConference getConference(String roomName) { roomName = roomName.toLowerCase(); // Other public methods which read from and/or write to the field // conferences are sychronized (e.g. conferenceEnded, conferenceRequest) // so synchronization is necessary here as well. synchronized (this) { return conferences.get(roomName); } }
/** * Allocates new focus for given MUC room. * * @param room the name of MUC room for which new conference has to be allocated. * @param properties configuration properties map included in the request. * @return <tt>true</tt> if conference focus is in the room and ready to handle session * participants. * @throws Exception if for any reason we have failed to create the conference */ public synchronized boolean conferenceRequest(String room, Map<String, String> properties) throws Exception { if (StringUtils.isNullOrEmpty(room)) return false; if (shutdownInProgress && !conferences.containsKey(room)) return false; if (!conferences.containsKey(room)) { createConference(room, properties); } JitsiMeetConference conference = conferences.get(room); return conference.isInTheRoom(); }
/** * Initializes and creates an account corresponding to the specified accountProperties. * * @param accountProperties a set of protocol (or implementation) specific properties defining the * new account. * @return the AccountID of the newly created account */ public AccountID createAccount(Map<String, String> accountProperties) { BundleContext bundleContext = getBundleContext(); if (bundleContext == null) throw new NullPointerException("The specified BundleContext was null"); if (accountProperties == null) throw new NullPointerException("The specified property map was null"); String userID = accountProperties.get(USER_ID); if (userID == null) throw new NullPointerException("The account properties contained no user id."); String protocolName = getProtocolName(); if (!accountProperties.containsKey(PROTOCOL)) accountProperties.put(PROTOCOL, protocolName); return createAccountID(userID, accountProperties); }
/** * Returns, the <tt>TransportManager</tt> instance for the channel-bundle with ID * <tt>channelBundleId</tt>. If no instance exists and <tt>create</tt> is <tt>true</tt>, one will * be created. * * @param channelBundleId the ID of the channel-bundle for which to return the * <tt>TransportManager</tt>. * @param create whether to create a new instance, if one doesn't exist. * @return the <tt>TransportManager</tt> instance for the channel-bundle with ID * <tt>channelBundleId</tt>. */ IceUdpTransportManager getTransportManager(String channelBundleId, boolean create) { IceUdpTransportManager transportManager; synchronized (transportManagers) { transportManager = transportManagers.get(channelBundleId); if (transportManager == null && create && !isExpired()) { try { // FIXME: the initiator is hard-coded // We assume rtcp-mux when bundle is used, so we make only // one component. transportManager = new IceUdpTransportManager(this, true, 1); } catch (IOException ioe) { throw new UndeclaredThrowableException(ioe); } transportManagers.put(channelBundleId, transportManager); } } return transportManager; }
/** * {@inheritDoc} * * <p>SCTP input data callback. */ @Override public void onSctpPacket( byte[] data, int sid, int ssn, int tsn, long ppid, int context, int flags) { if (ppid == WEB_RTC_PPID_CTRL) { // Channel control PPID try { onCtrlPacket(data, sid); } catch (IOException e) { logger.error("IOException when processing ctrl packet", e); } } else if (ppid == WEB_RTC_PPID_STRING || ppid == WEB_RTC_PPID_BIN) { WebRtcDataStream channel; synchronized (this) { channel = channels.get(sid); } if (channel == null) { logger.error("No channel found for sid: " + sid); return; } if (ppid == WEB_RTC_PPID_STRING) { // WebRTC String String str; String charsetName = "UTF-8"; try { str = new String(data, charsetName); } catch (UnsupportedEncodingException uee) { logger.error("Unsupported charset encoding/name " + charsetName, uee); str = null; } channel.onStringMsg(str); } else { // WebRTC Binary channel.onBinaryMsg(data); } } else { logger.warn("Got message on unsupported PPID: " + ppid); } }
/** * Handles control packet. * * @param data raw packet data that arrived on control PPID. * @param sid SCTP stream id on which the data has arrived. */ private synchronized void onCtrlPacket(byte[] data, int sid) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(data); int messageType = /* 1 byte unsigned integer */ 0xFF & buffer.get(); if (messageType == MSG_CHANNEL_ACK) { if (logger.isDebugEnabled()) { logger.debug(getEndpoint().getID() + " ACK received SID: " + sid); } // Open channel ACK WebRtcDataStream channel = channels.get(sid); if (channel != null) { // Ack check prevents from firing multiple notifications // if we get more than one ACKs (by mistake/bug). if (!channel.isAcknowledged()) { channel.ackReceived(); notifyChannelOpened(channel); } else { logger.warn("Redundant ACK received for SID: " + sid); } } else { logger.error("No channel exists on sid: " + sid); } } else if (messageType == MSG_OPEN_CHANNEL) { int channelType = /* 1 byte unsigned integer */ 0xFF & buffer.get(); int priority = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort(); long reliability = /* 4 bytes unsigned integer */ 0xFFFFFFFFL & buffer.getInt(); int labelLength = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort(); int protocolLength = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort(); String label; String protocol; if (labelLength == 0) { label = ""; } else { byte[] labelBytes = new byte[labelLength]; buffer.get(labelBytes); label = new String(labelBytes, "UTF-8"); } if (protocolLength == 0) { protocol = ""; } else { byte[] protocolBytes = new byte[protocolLength]; buffer.get(protocolBytes); protocol = new String(protocolBytes, "UTF-8"); } if (logger.isDebugEnabled()) { logger.debug( "!!! " + getEndpoint().getID() + " data channel open request on SID: " + sid + " type: " + channelType + " prio: " + priority + " reliab: " + reliability + " label: " + label + " proto: " + protocol); } if (channels.containsKey(sid)) { logger.error("Channel on sid: " + sid + " already exists"); } WebRtcDataStream newChannel = new WebRtcDataStream(sctpSocket, sid, label, true); channels.put(sid, newChannel); sendOpenChannelAck(sid); notifyChannelOpened(newChannel); } else { logger.error("Unexpected ctrl msg type: " + messageType); } }
/** * Returns {@link JitsiMeetConference} for given MUC <tt>roomName</tt> or <tt>null</tt> if no * conference has been allocated yet. * * @param roomName the name of MUC room for which we want get the {@link JitsiMeetConference} * instance. */ public JitsiMeetConference getConference(String roomName) { return conferences.get(roomName); }
/** * Retrieve DiscoverInfo for a specific node. * * @param caps the <tt>Caps</tt> i.e. the node, the hash and the ver * @return The corresponding DiscoverInfo or null if none is known. */ public static DiscoverInfo getDiscoverInfoByCaps(Caps caps) { synchronized (caps2discoverInfo) { DiscoverInfo discoverInfo = caps2discoverInfo.get(caps); /* * If we don't have the discoverInfo in the runtime cache yet, we * may have it remembered in a previous application instance. */ if (discoverInfo == null) { ConfigurationService configurationService = getConfigService(); String capsPropertyName = getCapsPropertyName(caps); String xml = configurationService.getString(capsPropertyName); if ((xml != null) && (xml.length() != 0)) { IQProvider discoverInfoProvider = (IQProvider) ProviderManager.getInstance() .getIQProvider("query", "http://jabber.org/protocol/disco#info"); if (discoverInfoProvider != null) { XmlPullParser parser = new MXParser(); try { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new StringReader(xml)); // Start the parser. parser.next(); } catch (XmlPullParserException xppex) { parser = null; } catch (IOException ioex) { parser = null; } if (parser != null) { try { discoverInfo = (DiscoverInfo) discoverInfoProvider.parseIQ(parser); } catch (Exception ex) { } if (discoverInfo != null) { if (caps.isValid(discoverInfo)) caps2discoverInfo.put(caps, discoverInfo); else { logger.error( "Invalid DiscoverInfo for " + caps.getNodeVer() + ": " + discoverInfo); /* * The discoverInfo doesn't seem valid * according to the caps which means that we * must have stored invalid information. * Delete the invalid information in order * to not try to validate it again. */ configurationService.removeProperty(capsPropertyName); } } } } } } return discoverInfo; } }
/** * Get the discover info given a user name. The discover info is returned if the user has a * node#ver associated with it and the node#ver has a discover info associated with it. * * @param user user name (Full JID) * @return the discovered info */ public DiscoverInfo getDiscoverInfoByUser(String user) { Caps caps = userCaps.get(user); return (caps == null) ? null : getDiscoverInfoByCaps(caps); }
/** * Gets the <tt>Caps</tt> i.e. the node, the hash and the ver of a user. * * @param user the user (Full JID) * @return the <tt>Caps</tt> i.e. the node, the hash and the ver of <tt>user</tt> */ public Caps getCapsByUser(String user) { return userCaps.get(user); }