/** * Handles a specific <tt>IOException</tt> which was thrown during the execution of {@link * #runInConnectThread(DTLSProtocol, TlsPeer, DatagramTransport)} while trying to establish a DTLS * connection * * @param ioe the <tt>IOException</tt> to handle * @param msg the human-readable message to log about the specified <tt>ioe</tt> * @param i the number of tries remaining after the current one * @return <tt>true</tt> if the specified <tt>ioe</tt> was successfully handled; <tt>false</tt>, * otherwise */ private boolean handleRunInConnectThreadException(IOException ioe, String msg, int i) { // SrtpControl.start(MediaType) starts its associated TransformEngine. // We will use that mediaType to signal the normal stop then as well // i.e. we will ignore exception after the procedure to stop this // PacketTransformer has begun. if (mediaType == null) return false; if (ioe instanceof TlsFatalAlert) { TlsFatalAlert tfa = (TlsFatalAlert) ioe; short alertDescription = tfa.getAlertDescription(); if (alertDescription == AlertDescription.unexpected_message) { msg += " Received fatal unexpected message."; if (i == 0 || !Thread.currentThread().equals(connectThread) || connector == null || mediaType == null) { msg += " Giving up after " + (CONNECT_TRIES - i) + " retries."; } else { msg += " Will retry."; logger.error(msg, ioe); return true; } } else { msg += " Received fatal alert " + alertDescription + "."; } } logger.error(msg, ioe); return false; }
/** Implements notification in order to track socket state. */ @Override public synchronized void onSctpNotification(SctpSocket socket, SctpNotification notification) { if (logger.isDebugEnabled()) { logger.debug("socket=" + socket + "; notification=" + notification); } switch (notification.sn_type) { case SctpNotification.SCTP_ASSOC_CHANGE: SctpNotification.AssociationChange assocChange = (SctpNotification.AssociationChange) notification; switch (assocChange.state) { case SctpNotification.AssociationChange.SCTP_COMM_UP: if (!assocIsUp) { boolean wasReady = isReady(); assocIsUp = true; if (isReady() && !wasReady) notifySctpConnectionReady(); } break; case SctpNotification.AssociationChange.SCTP_COMM_LOST: case SctpNotification.AssociationChange.SCTP_SHUTDOWN_COMP: case SctpNotification.AssociationChange.SCTP_CANT_STR_ASSOC: try { closeStream(); } catch (IOException e) { logger.error("Error closing SCTP socket", e); } break; } break; } }
/** * Start the swing notification service * * @param bc * @throws java.lang.Exception */ public void start(BundleContext bc) throws Exception { if (logger.isInfoEnabled()) logger.info("Swing Notification ...[ STARTING ]"); bundleContext = bc; PopupMessageHandler handler = null; handler = new PopupMessageHandlerSwingImpl(); getConfigurationService(); bc.registerService(PopupMessageHandler.class.getName(), handler, null); if (logger.isInfoEnabled()) logger.info("Swing Notification ...[REGISTERED]"); }
/** Stops this <tt>PacketTransformer</tt>. */ private synchronized void stop() { if (connectThread != null) connectThread = null; try { // The dtlsTransport and _srtpTransformer SHOULD be closed, of // course. The datagramTransport MUST be closed. if (dtlsTransport != null) { try { dtlsTransport.close(); } catch (IOException ioe) { logger.error("Failed to (properly) close " + dtlsTransport.getClass(), ioe); } dtlsTransport = null; } if (_srtpTransformer != null) { _srtpTransformer.close(); _srtpTransformer = null; } } finally { try { closeDatagramTransport(); } finally { notifyAll(); } } }
@Override /** * Returns the size of the image in bytes. * * @param sourceString the image link. * @return the file size in bytes of the image link provided; -1 if the size isn't available or * exceeds the max allowed image size. */ public int getImageSize(String sourceString) { int length = -1; try { URL url = new URL(sourceString); String protocol = url.getProtocol(); if (protocol.equals("http") || protocol.equals("https")) { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); length = connection.getContentLength(); connection.disconnect(); } else if (protocol.equals("ftp")) { FTPUtils ftp = new FTPUtils(sourceString); length = ftp.getSize(); ftp.disconnect(); } if (length > imgMaxSize) { length = -1; } } catch (Exception e) { logger.debug("Failed to get the length of the image in bytes", e); } return length; }
/** * Starts the configuration service * * @param bundleContext the <tt>BundleContext</tt> as provided by the OSGi framework. * @throws Exception if anything goes wrong */ public void start(BundleContext bundleContext) throws Exception { FileAccessService fas = ServiceUtils.getService(bundleContext, FileAccessService.class); if (fas != null) { File usePropFileConfig; try { usePropFileConfig = fas.getPrivatePersistentFile(".usepropfileconfig", FileCategory.PROFILE); } catch (Exception ise) { // There is somewhat of a chicken-and-egg dependency between // FileConfigurationServiceImpl and ConfigurationServiceImpl: // FileConfigurationServiceImpl throws IllegalStateException if // certain System properties are not set, // ConfigurationServiceImpl will make sure that these properties // are set but it will do that later. // A SecurityException is thrown when the destination // is not writable or we do not have access to that folder usePropFileConfig = null; } if (usePropFileConfig != null && usePropFileConfig.exists()) { logger.info("Using properties file configuration store."); this.cs = LibJitsi.getConfigurationService(); } } if (this.cs == null) { this.cs = new JdbcConfigService(fas); } bundleContext.registerService(ConfigurationService.class.getName(), this.cs, null); fixPermissions(this.cs); }
/** * Returns the cached recent messages history. * * @return * @throws IOException */ private History getHistory() throws IOException { synchronized (historyID) { HistoryService historyService = MessageHistoryActivator.getMessageHistoryService().getHistoryService(); if (history == null) { history = historyService.createHistory(historyID, recordStructure); // lets check the version if not our version, re-create // history (delete it) HistoryReader reader = history.getReader(); boolean delete = false; QueryResultSet<HistoryRecord> res = reader.findLast(1); if (res != null && res.hasNext()) { HistoryRecord hr = res.next(); if (hr.getPropertyValues().length >= 4) { if (!hr.getPropertyValues()[3].equals(RECENT_MSGS_VER)) delete = true; } else delete = true; } if (delete) { // delete it try { historyService.purgeLocallyStoredHistory(historyID); history = historyService.createHistory(historyID, recordStructure); } catch (IOException ex) { logger.error("Cannot delete recent_messages history", ex); } } } return history; } }
/** Prepares the factory for bundle shutdown. */ public void stop() { if (logger.isTraceEnabled()) logger.trace("Preparing to stop all protocol providers of" + this); synchronized (registeredAccounts) { for (Enumeration<ServiceRegistration> registrations = registeredAccounts.elements(); registrations.hasMoreElements(); ) { ServiceRegistration reg = registrations.nextElement(); stop(reg); reg.unregister(); } registeredAccounts.clear(); } }
/** * Gets the max allowed size value in bytes from Configuration service and sets the value to * <tt>imgMaxSize</tt> if succeed. If the configuration property isn't available or the value * can't be parsed correctly the value of <tt>imgMaxSize</tt> isn't changed. */ private void setMaxImgSizeFromConf() { ConfigurationService configService = DirectImageActivator.getConfigService(); if (configService != null) { String confImgSizeStr = (String) configService.getProperty(MAX_IMG_SIZE); try { if (confImgSizeStr != null) { imgMaxSize = Long.parseLong(confImgSizeStr); } else { configService.setProperty(MAX_IMG_SIZE, imgMaxSize); } } catch (NumberFormatException e) { if (logger.isDebugEnabled()) logger.debug( "Failed to parse max image size: " + confImgSizeStr + ". Going for default."); } } }
/** * Sends acknowledgment for open channel request on given SCTP stream ID. * * @param sid SCTP stream identifier to be used for sending ack. */ private void sendOpenChannelAck(int sid) throws IOException { // Send ACK byte[] ack = MSG_CHANNEL_ACK_BYTES; int sendAck = sctpSocket.send(ack, true, sid, WEB_RTC_PPID_CTRL); if (sendAck != ack.length) { logger.error("Failed to send open channel confirmation"); } }
/** * Computes and returns the hash of the specified <tt>capsString</tt> using the specified * <tt>hashAlgorithm</tt>. * * @param hashAlgorithm the name of the algorithm to be used to generate the hash * @param capsString the capabilities string that we'd like to compute a hash for. * @return the hash of <tt>capsString</tt> computed by the specified <tt>hashAlgorithm</tt> or * <tt>null</tt> if generating the hash has failed */ private static String capsToHash(String hashAlgorithm, String capsString) { try { MessageDigest md = MessageDigest.getInstance(hashAlgorithm); byte[] digest = md.digest(capsString.getBytes()); return Base64.encodeBytes(digest); } catch (NoSuchAlgorithmException nsae) { logger.error("Unsupported XEP-0115: Entity Capabilities hash algorithm: " + hashAlgorithm); return null; } }
/** * {@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); } }
/** * Closes {@link #datagramTransport} if it is non-<tt>null</tt> and logs and swallows any * <tt>IOException</tt>. */ private void closeDatagramTransport() { if (datagramTransport != null) { try { datagramTransport.close(); } catch (IOException ioe) { // DatagramTransportImpl has no reason to fail because it is // merely an adapter of #connector and this PacketTransformer to // the terms of the Bouncy Castle Crypto API. logger.error("Failed to (properly) close " + datagramTransport.getClass(), ioe); } datagramTransport = null; } }
/** * Create preview component. * * @param type type * @param comboBox the options. * @param prefSize the preferred size * @return the component. */ private static Component createPreview(int type, final JComboBox comboBox, Dimension prefSize) { JComponent preview = null; if (type == DeviceConfigurationComboBoxModel.AUDIO) { Object selectedItem = comboBox.getSelectedItem(); if (selectedItem instanceof AudioSystem) { AudioSystem audioSystem = (AudioSystem) selectedItem; if (!NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(audioSystem.getLocatorProtocol())) { preview = new TransparentPanel(new GridBagLayout()); createAudioSystemControls(audioSystem, preview); } } } else if (type == DeviceConfigurationComboBoxModel.VIDEO) { JLabel noPreview = new JLabel( NeomediaActivator.getResources().getI18NString("impl.media.configform.NO_PREVIEW")); noPreview.setHorizontalAlignment(SwingConstants.CENTER); noPreview.setVerticalAlignment(SwingConstants.CENTER); preview = createVideoContainer(noPreview); preview.setPreferredSize(prefSize); Object selectedItem = comboBox.getSelectedItem(); CaptureDeviceInfo device = null; if (selectedItem instanceof DeviceConfigurationComboBoxModel.CaptureDevice) device = ((DeviceConfigurationComboBoxModel.CaptureDevice) selectedItem).info; Exception exception; try { createVideoPreview(device, preview); exception = null; } catch (IOException ex) { exception = ex; } catch (MediaException ex) { exception = ex; } if (exception != null) { logger.error("Failed to create preview for device " + device, exception); device = null; } } return preview; }
/** * Searches for contact ids in history of recent messages. * * @param provider * @param after * @return */ List<String> getRecentContactIDs(String provider, Date after) { List<String> res = new ArrayList<String>(); try { History history = getHistory(); if (history != null) { Iterator<HistoryRecord> recs = history.getReader().findLast(NUMBER_OF_MSGS_IN_HISTORY); SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT); while (recs.hasNext()) { HistoryRecord hr = recs.next(); String contact = null; String recordProvider = null; Date timestamp = null; for (int i = 0; i < hr.getPropertyNames().length; i++) { String propName = hr.getPropertyNames()[i]; if (propName.equals(STRUCTURE_NAMES[0])) recordProvider = hr.getPropertyValues()[i]; else if (propName.equals(STRUCTURE_NAMES[1])) contact = hr.getPropertyValues()[i]; else if (propName.equals(STRUCTURE_NAMES[2])) { try { timestamp = sdf.parse(hr.getPropertyValues()[i]); } catch (ParseException e) { timestamp = new Date(Long.parseLong(hr.getPropertyValues()[i])); } } } if (recordProvider == null || contact == null) continue; if (after != null && timestamp != null && timestamp.before(after)) continue; if (recordProvider.equals(provider)) res.add(contact); } } } catch (IOException ex) { logger.error("cannot create recent_messages history", ex); } return res; }
/** * Removes the specified account from the list of accounts that this provider factory is handling. * If the specified accountID is unknown to the ProtocolProviderFactory, the call has no effect * and false is returned. This method is persistent in nature and once called the account * corresponding to the specified ID will not be loaded during future runs of the project. * * @param accountID the ID of the account to remove. * @return true if an account with the specified ID existed and was removed and false otherwise. */ public boolean uninstallAccount(AccountID accountID) { // Unregister the protocol provider. ServiceReference serRef = getProviderForAccount(accountID); boolean wasAccountExisting = false; // If the protocol provider service is registered, first unregister the // service. if (serRef != null) { BundleContext bundleContext = getBundleContext(); ProtocolProviderService protocolProvider = (ProtocolProviderService) bundleContext.getService(serRef); try { protocolProvider.unregister(); } catch (OperationFailedException ex) { logger.error( "Failed to unregister protocol provider for account: " + accountID + " caused by: " + ex); } } ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.remove(accountID); } // first remove the stored account so when PP is unregistered we can // distinguish between deleted or just disabled account wasAccountExisting = removeStoredAccount(accountID); if (registration != null) { // Kill the service. registration.unregister(); } return wasAccountExisting; }
/** * Returns true if the content type of the resource pointed by sourceString is an image. * * @param sourceString the original image link. * @return true if the content type of the resource pointed by sourceString is an image. */ @Override public boolean isDirectImage(String sourceString) { boolean isDirectImage = false; try { URL url = new URL(sourceString); String protocol = url.getProtocol(); if (protocol.equals("http") || protocol.equals("https")) { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); isDirectImage = connection.getContentType().contains("image"); connection.disconnect(); } else if (protocol.equals("ftp")) { if (sourceString.endsWith(".png") || sourceString.endsWith(".jpg") || sourceString.endsWith(".gif")) { isDirectImage = true; } } } catch (Exception e) { logger.debug("Failed to retrieve content type information for" + sourceString, e); } return isDirectImage; }
/** * Sends the data contained in a specific byte array as application data through the DTLS * connection of this <tt>DtlsPacketTransformer</tt>. * * @param buf the byte array containing data to send. * @param off the offset in <tt>buf</tt> where the data begins. * @param len the length of data to send. */ public void sendApplicationData(byte[] buf, int off, int len) { DTLSTransport dtlsTransport = this.dtlsTransport; Throwable throwable = null; if (dtlsTransport != null) { try { dtlsTransport.send(buf, off, len); } catch (IOException ioe) { throwable = ioe; } } else { throwable = new NullPointerException("dtlsTransport"); } if (throwable != null) { // SrtpControl.start(MediaType) starts its associated // TransformEngine. We will use that mediaType to signal the normal // stop then as well i.e. we will ignore exception after the // procedure to stop this PacketTransformer has begun. if (mediaType != null && !tlsPeerHasRaisedCloseNotifyWarning) { logger.error("Failed to send application data over DTLS transport: ", throwable); } } }
/** Adds recent message in history. */ private void saveRecentMessageToHistory(ComparableEvtObj msc) { synchronized (historyID) { // and create it try { History history = getHistory(); HistoryWriter writer = history.getWriter(); SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT); writer.addRecord( new String[] { msc.getProtocolProviderService().getAccountID().getAccountUniqueID(), msc.getContactAddress(), sdf.format(msc.getTimestamp()), RECENT_MSGS_VER }, NUMBER_OF_MSGS_IN_HISTORY); } catch (IOException ex) { logger.error("cannot create recent_messages history", ex); return; } } }
/** * Makes home folder and the configuration file readable and writable only to the owner. * * @param cs the <tt>ConfigurationService</tt> instance to check for home folder and configuration * file. */ private static void fixPermissions(ConfigurationService cs) { if (!OSUtils.IS_LINUX && !OSUtils.IS_MAC) return; try { // let's check config file and config folder File homeFolder = new File(cs.getScHomeDirLocation(), cs.getScHomeDirName()); Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>() { { add(PosixFilePermission.OWNER_READ); add(PosixFilePermission.OWNER_WRITE); add(PosixFilePermission.OWNER_EXECUTE); } }; Files.setPosixFilePermissions(Paths.get(homeFolder.getAbsolutePath()), perms); String fileName = cs.getConfigurationFilename(); if (fileName != null) { File cf = new File(homeFolder, fileName); if (cf.exists()) { perms = new HashSet<PosixFilePermission>() { { add(PosixFilePermission.OWNER_READ); add(PosixFilePermission.OWNER_WRITE); } }; Files.setPosixFilePermissions(Paths.get(cf.getAbsolutePath()), perms); } } } catch (Throwable t) { logger.error("Error creating c lib instance for fixing file permissions", t); if (t instanceof InterruptedException) Thread.currentThread().interrupt(); else if (t instanceof ThreadDeath) throw (ThreadDeath) t; } }
/** * Unloads the account corresponding to the given <tt>accountID</tt>. Unregisters the * corresponding protocol provider, but keeps the account in contrast to the uninstallAccount * method. * * @param accountID the account identifier * @return true if an account with the specified ID existed and was unloaded and false otherwise. */ public boolean unloadAccount(AccountID accountID) { // Unregister the protocol provider. ServiceReference serRef = getProviderForAccount(accountID); if (serRef == null) { return false; } BundleContext bundleContext = getBundleContext(); ProtocolProviderService protocolProvider = (ProtocolProviderService) bundleContext.getService(serRef); try { protocolProvider.unregister(); } catch (OperationFailedException ex) { logger.error( "Failed to unregister protocol provider for account : " + accountID + " caused by: " + ex); } ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.remove(accountID); } if (registration == null) { return false; } // Kill the service. registration.unregister(); return true; }
/** * Class is a transport layer for WebRTC data channels. It consists of SCTP connection running on * top of ICE/DTLS layer. Manages WebRTC data channels. See * http://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-08 for more info on WebRTC data * channels. * * <p>Control protocol: http://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-03 FIXME handle * closing of data channels(SCTP stream reset) * * @author Pawel Domas * @author Lyubomir Marinov * @author Boris Grozev */ public class SctpConnection extends Channel implements SctpDataCallback, SctpSocket.NotificationListener { /** Generator used to track debug IDs. */ private static int debugIdGen = -1; /** DTLS transport buffer size. Note: randomly chosen. */ private static final int DTLS_BUFFER_SIZE = 2048; /** Switch used for debugging SCTP traffic purposes. FIXME to be removed */ private static final boolean LOG_SCTP_PACKETS = false; /** The logger */ private static final Logger logger = Logger.getLogger(SctpConnection.class); /** * Message type used to acknowledge WebRTC data channel allocation on SCTP stream ID on which * <tt>MSG_OPEN_CHANNEL</tt> message arrives. */ private static final int MSG_CHANNEL_ACK = 0x2; private static final byte[] MSG_CHANNEL_ACK_BYTES = new byte[] {MSG_CHANNEL_ACK}; /** * Message with this type sent over control PPID in order to open new WebRTC data channel on SCTP * stream ID that this message is sent. */ private static final int MSG_OPEN_CHANNEL = 0x3; /** SCTP transport buffer size. */ private static final int SCTP_BUFFER_SIZE = DTLS_BUFFER_SIZE - 13; /** The pool of <tt>Thread</tt>s which run <tt>SctpConnection</tt>s. */ private static final ExecutorService threadPool = ExecutorUtils.newCachedThreadPool(true, SctpConnection.class.getName()); /** Payload protocol id that identifies binary data in WebRTC data channel. */ static final int WEB_RTC_PPID_BIN = 53; /** Payload protocol id for control data. Used for <tt>WebRtcDataStream</tt> allocation. */ static final int WEB_RTC_PPID_CTRL = 50; /** Payload protocol id that identifies text data UTF8 encoded in WebRTC data channels. */ static final int WEB_RTC_PPID_STRING = 51; /** * The <tt>String</tt> value of the <tt>Protocol</tt> field of the <tt>DATA_CHANNEL_OPEN</tt> * message. */ private static final String WEBRTC_DATA_CHANNEL_PROTOCOL = "http://jitsi.org/protocols/colibri"; private static synchronized int generateDebugId() { debugIdGen += 2; return debugIdGen; } /** * Indicates whether the STCP association is ready and has not been ended by a subsequent state * change. */ private boolean assocIsUp; /** Indicates if we have accepted incoming connection. */ private boolean acceptedIncomingConnection; /** Data channels mapped by SCTP stream identified(sid). */ private final Map<Integer, WebRtcDataStream> channels = new HashMap<Integer, WebRtcDataStream>(); /** Debug ID used to distinguish SCTP sockets in packet logs. */ private final int debugId; /** * The <tt>AsyncExecutor</tt> which is to asynchronously dispatch the events fired by this * instance in order to prevent possible listeners from blocking this <tt>SctpConnection</tt> in * general and {@link #sctpSocket} in particular for too long. The timeout of <tt>15</tt> is * chosen to be in accord with the time it takes to expire a <tt>Channel</tt>. */ private final AsyncExecutor<Runnable> eventDispatcher = new AsyncExecutor<Runnable>(15, TimeUnit.MILLISECONDS); /** Datagram socket for ICE/UDP layer. */ private IceSocketWrapper iceSocket; /** * List of <tt>WebRtcDataStreamListener</tt>s that will be notified whenever new WebRTC data * channel is opened. */ private final List<WebRtcDataStreamListener> listeners = new ArrayList<WebRtcDataStreamListener>(); /** Remote SCTP port. */ private final int remoteSctpPort; /** <tt>SctpSocket</tt> used for SCTP transport. */ private SctpSocket sctpSocket; /** * Flag prevents from starting this connection multiple times from {@link #maybeStartStream()}. */ private boolean started; /** * Initializes a new <tt>SctpConnection</tt> instance. * * @param id the string identifier of this connection instance * @param content the <tt>Content</tt> which is initializing the new instance * @param endpoint the <tt>Endpoint</tt> of newly created instance * @param remoteSctpPort the SCTP port used by remote peer * @param channelBundleId the ID of the channel-bundle this <tt>SctpConnection</tt> is to be a * part of (or <tt>null</tt> if no it is not to be a part of a channel-bundle). * @throws Exception if an error occurs while initializing the new instance */ public SctpConnection( String id, Content content, Endpoint endpoint, int remoteSctpPort, String channelBundleId) throws Exception { super(content, id, channelBundleId); setEndpoint(endpoint.getID()); this.remoteSctpPort = remoteSctpPort; this.debugId = generateDebugId(); } /** * Adds <tt>WebRtcDataStreamListener</tt> to the list of listeners. * * @param listener the <tt>WebRtcDataStreamListener</tt> to be added to the listeners list. */ public void addChannelListener(WebRtcDataStreamListener listener) { if (listener == null) { throw new NullPointerException("listener"); } else { synchronized (listeners) { if (!listeners.contains(listener)) { listeners.add(listener); } } } } /** {@inheritDoc} */ @Override protected void closeStream() throws IOException { try { synchronized (this) { assocIsUp = false; acceptedIncomingConnection = false; if (sctpSocket != null) { sctpSocket.close(); sctpSocket = null; } } } finally { if (iceSocket != null) { // It is now the responsibility of the transport manager to // close the socket. // iceUdpSocket.close(); } } } /** {@inheritDoc} */ @Override public void expire() { try { eventDispatcher.shutdown(); } finally { super.expire(); } } /** * Gets the <tt>WebRtcDataStreamListener</tt>s added to this instance. * * @return the <tt>WebRtcDataStreamListener</tt>s added to this instance or <tt>null</tt> if there * are no <tt>WebRtcDataStreamListener</tt>s added to this instance */ private WebRtcDataStreamListener[] getChannelListeners() { WebRtcDataStreamListener[] ls; synchronized (listeners) { if (listeners.isEmpty()) { ls = null; } else { ls = listeners.toArray(new WebRtcDataStreamListener[listeners.size()]); } } return ls; } /** * 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; } /** * Returns <tt>true</tt> if this <tt>SctpConnection</tt> is connected to the remote peer and * operational. * * @return <tt>true</tt> if this <tt>SctpConnection</tt> is connected to the remote peer and * operational */ public boolean isReady() { return assocIsUp && acceptedIncomingConnection; } /** {@inheritDoc} */ @Override protected void maybeStartStream() throws IOException { // connector final StreamConnector connector = getStreamConnector(); if (connector == null) return; synchronized (this) { if (started) return; threadPool.execute( new Runnable() { @Override public void run() { try { Sctp.init(); runOnDtlsTransport(connector); } catch (IOException e) { logger.error(e, e); } finally { try { Sctp.finish(); } catch (IOException e) { logger.error("Failed to shutdown SCTP stack", e); } } } }); started = true; } } /** * Submits {@link #notifyChannelOpenedInEventDispatcher(WebRtcDataStream)} to {@link * #eventDispatcher} for asynchronous execution. * * @param dataChannel */ private void notifyChannelOpened(final WebRtcDataStream dataChannel) { if (!isExpired()) { eventDispatcher.execute( new Runnable() { @Override public void run() { notifyChannelOpenedInEventDispatcher(dataChannel); } }); } } private void notifyChannelOpenedInEventDispatcher(WebRtcDataStream dataChannel) { /* * When executing asynchronously in eventDispatcher, it is technically * possible that this SctpConnection may have expired by now. */ if (!isExpired()) { WebRtcDataStreamListener[] ls = getChannelListeners(); if (ls != null) { for (WebRtcDataStreamListener l : ls) { l.onChannelOpened(this, dataChannel); } } } } /** * Submits {@link #notifySctpConnectionReadyInEventDispatcher()} to {@link #eventDispatcher} for * asynchronous execution. */ private void notifySctpConnectionReady() { if (!isExpired()) { eventDispatcher.execute( new Runnable() { @Override public void run() { notifySctpConnectionReadyInEventDispatcher(); } }); } } /** * Notifies the <tt>WebRtcDataStreamListener</tt>s added to this instance that this * <tt>SctpConnection</tt> is ready i.e. it is connected to the remote peer and operational. */ private void notifySctpConnectionReadyInEventDispatcher() { /* * When executing asynchronously in eventDispatcher, it is technically * possible that this SctpConnection may have expired by now. */ if (!isExpired() && isReady()) { WebRtcDataStreamListener[] ls = getChannelListeners(); if (ls != null) { for (WebRtcDataStreamListener l : ls) { l.onSctpConnectionReady(this); } } } } /** * 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); } } /** {@inheritDoc} */ @Override protected void onEndpointChanged(Endpoint oldValue, Endpoint newValue) { if (oldValue != null) oldValue.setSctpConnection(null); if (newValue != null) newValue.setSctpConnection(this); } /** Implements notification in order to track socket state. */ @Override public synchronized void onSctpNotification(SctpSocket socket, SctpNotification notification) { if (logger.isDebugEnabled()) { logger.debug("socket=" + socket + "; notification=" + notification); } switch (notification.sn_type) { case SctpNotification.SCTP_ASSOC_CHANGE: SctpNotification.AssociationChange assocChange = (SctpNotification.AssociationChange) notification; switch (assocChange.state) { case SctpNotification.AssociationChange.SCTP_COMM_UP: if (!assocIsUp) { boolean wasReady = isReady(); assocIsUp = true; if (isReady() && !wasReady) notifySctpConnectionReady(); } break; case SctpNotification.AssociationChange.SCTP_COMM_LOST: case SctpNotification.AssociationChange.SCTP_SHUTDOWN_COMP: case SctpNotification.AssociationChange.SCTP_CANT_STR_ASSOC: try { closeStream(); } catch (IOException e) { logger.error("Error closing SCTP socket", e); } break; } break; } } /** * {@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); } } /** * Opens new WebRTC data channel using specified parameters. * * @param type channel type as defined in control protocol description. Use 0 for "reliable". * @param prio channel priority. The higher the number, the lower the priority. * @param reliab Reliability Parameter<br> * This field is ignored if a reliable channel is used. If a partial reliable channel with * limited number of retransmissions is used, this field specifies the number of * retransmissions. If a partial reliable channel with limited lifetime is used, this field * specifies the maximum lifetime in milliseconds. The following table summarizes this:<br> * </br> * <p>+------------------------------------------------+------------------+ | Channel Type | * Reliability | | | Parameter | * +------------------------------------------------+------------------+ | * DATA_CHANNEL_RELIABLE | Ignored | | DATA_CHANNEL_RELIABLE_UNORDERED | Ignored | | * DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT | Number of RTX | | * DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED | Number of RTX | | * DATA_CHANNEL_PARTIAL_RELIABLE_TIMED | Lifetime in ms | | * DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED | Lifetime in ms | * +------------------------------------------------+------------------+ * @param sid SCTP stream id that will be used by new channel (it must not be already used). * @param label text label for the channel. * @return new instance of <tt>WebRtcDataStream</tt> that represents opened WebRTC data channel. * @throws IOException if IO error occurs. */ public synchronized WebRtcDataStream openChannel( int type, int prio, long reliab, int sid, String label) throws IOException { if (channels.containsKey(sid)) { throw new IOException("Channel on sid: " + sid + " already exists"); } // Label Length & Label byte[] labelBytes; int labelByteLength; if (label == null) { labelBytes = null; labelByteLength = 0; } else { labelBytes = label.getBytes("UTF-8"); labelByteLength = labelBytes.length; if (labelByteLength > 0xFFFF) labelByteLength = 0xFFFF; } // Protocol Length & Protocol String protocol = WEBRTC_DATA_CHANNEL_PROTOCOL; byte[] protocolBytes; int protocolByteLength; if (protocol == null) { protocolBytes = null; protocolByteLength = 0; } else { protocolBytes = protocol.getBytes("UTF-8"); protocolByteLength = protocolBytes.length; if (protocolByteLength > 0xFFFF) protocolByteLength = 0xFFFF; } ByteBuffer packet = ByteBuffer.allocate(12 + labelByteLength + protocolByteLength); // Message open new channel on current sid // Message Type packet.put((byte) MSG_OPEN_CHANNEL); // Channel Type packet.put((byte) type); // Priority packet.putShort((short) prio); // Reliability Parameter packet.putInt((int) reliab); // Label Length packet.putShort((short) labelByteLength); // Protocol Length packet.putShort((short) protocolByteLength); // Label if (labelByteLength != 0) { packet.put(labelBytes, 0, labelByteLength); } // Protocol if (protocolByteLength != 0) { packet.put(protocolBytes, 0, protocolByteLength); } int sentCount = sctpSocket.send(packet.array(), true, sid, WEB_RTC_PPID_CTRL); if (sentCount != packet.capacity()) { throw new IOException("Failed to open new chanel on sid: " + sid); } WebRtcDataStream channel = new WebRtcDataStream(sctpSocket, sid, label, false); channels.put(sid, channel); return channel; } /** * Removes <tt>WebRtcDataStreamListener</tt> from the list of listeners. * * @param listener the <tt>WebRtcDataStreamListener</tt> to be removed from the listeners list. */ public void removeChannelListener(WebRtcDataStreamListener listener) { if (listener != null) { synchronized (listeners) { listeners.remove(listener); } } } private void runOnDtlsTransport(StreamConnector connector) throws IOException { DtlsControlImpl dtlsControl = (DtlsControlImpl) getTransportManager().getDtlsControl(this); DtlsTransformEngine engine = dtlsControl.getTransformEngine(); final DtlsPacketTransformer transformer = (DtlsPacketTransformer) engine.getRTPTransformer(); byte[] receiveBuffer = new byte[SCTP_BUFFER_SIZE]; if (LOG_SCTP_PACKETS) { System.setProperty( ConfigurationService.PNAME_SC_HOME_DIR_LOCATION, System.getProperty("java.io.tmpdir")); System.setProperty( ConfigurationService.PNAME_SC_HOME_DIR_NAME, SctpConnection.class.getName()); } synchronized (this) { // FIXME local SCTP port is hardcoded in bridge offer SDP (Jitsi // Meet) sctpSocket = Sctp.createSocket(5000); assocIsUp = false; acceptedIncomingConnection = false; } // Implement output network link for SCTP stack on DTLS transport sctpSocket.setLink( new NetworkLink() { @Override public void onConnOut(SctpSocket s, byte[] packet) throws IOException { if (LOG_SCTP_PACKETS) { LibJitsi.getPacketLoggingService() .logPacket( PacketLoggingService.ProtocolName.ICE4J, new byte[] {0, 0, 0, (byte) debugId}, 5000, new byte[] {0, 0, 0, (byte) (debugId + 1)}, remoteSctpPort, PacketLoggingService.TransportName.UDP, true, packet); } // Send through DTLS transport transformer.sendApplicationData(packet, 0, packet.length); } }); if (logger.isDebugEnabled()) { logger.debug("Connecting SCTP to port: " + remoteSctpPort + " to " + getEndpoint().getID()); } sctpSocket.setNotificationListener(this); sctpSocket.listen(); // FIXME manage threads threadPool.execute( new Runnable() { @Override public void run() { SctpSocket sctpSocket = null; try { // sctpSocket is set to null on close sctpSocket = SctpConnection.this.sctpSocket; while (sctpSocket != null) { if (sctpSocket.accept()) { acceptedIncomingConnection = true; break; } Thread.sleep(100); sctpSocket = SctpConnection.this.sctpSocket; } if (isReady()) { notifySctpConnectionReady(); } } catch (Exception e) { logger.error("Error accepting SCTP connection", e); } if (sctpSocket == null && logger.isInfoEnabled()) { logger.info( "SctpConnection " + getID() + " closed" + " before SctpSocket accept()-ed."); } } }); // Notify that from now on SCTP connection is considered functional sctpSocket.setDataCallback(this); // Setup iceSocket DatagramSocket datagramSocket = connector.getDataSocket(); if (datagramSocket != null) { this.iceSocket = new IceUdpSocketWrapper(datagramSocket); } else { this.iceSocket = new IceTcpSocketWrapper(connector.getDataTCPSocket()); } DatagramPacket rcvPacket = new DatagramPacket(receiveBuffer, 0, receiveBuffer.length); // Receive loop, breaks when SCTP socket is closed try { do { iceSocket.receive(rcvPacket); RawPacket raw = new RawPacket(rcvPacket.getData(), rcvPacket.getOffset(), rcvPacket.getLength()); raw = transformer.reverseTransform(raw); // Check for app data if (raw == null) continue; if (LOG_SCTP_PACKETS) { LibJitsi.getPacketLoggingService() .logPacket( PacketLoggingService.ProtocolName.ICE4J, new byte[] {0, 0, 0, (byte) (debugId + 1)}, remoteSctpPort, new byte[] {0, 0, 0, (byte) debugId}, 5000, PacketLoggingService.TransportName.UDP, false, raw.getBuffer(), raw.getOffset(), raw.getLength()); } // Pass network packet to SCTP stack sctpSocket.onConnIn(raw.getBuffer(), raw.getOffset(), raw.getLength()); } while (true); } finally { // Eventually, close the socket although it should happen from // expire(). synchronized (this) { assocIsUp = false; acceptedIncomingConnection = false; if (sctpSocket != null) { sctpSocket.close(); sctpSocket = null; } } } } /** * Sends acknowledgment for open channel request on given SCTP stream ID. * * @param sid SCTP stream identifier to be used for sending ack. */ private void sendOpenChannelAck(int sid) throws IOException { // Send ACK byte[] ack = MSG_CHANNEL_ACK_BYTES; int sendAck = sctpSocket.send(ack, true, sid, WEB_RTC_PPID_CTRL); if (sendAck != ack.length) { logger.error("Failed to send open channel confirmation"); } } /** * {@inheritDoc} * * <p>Creates a <tt>TransportManager</tt> instance suitable for an <tt>SctpConnection</tt> (e.g. * with 1 component only). */ protected TransportManager createTransportManager(String xmlNamespace) throws IOException { if (IceUdpTransportPacketExtension.NAMESPACE.equals(xmlNamespace)) { Content content = getContent(); return new IceUdpTransportManager( content.getConference(), isInitiator(), 1 /* num components */, content.getName()); } else if (RawUdpTransportPacketExtension.NAMESPACE.equals(xmlNamespace)) { // TODO: support RawUdp once RawUdpTransportManager is updated // return new RawUdpTransportManager(this); throw new IllegalArgumentException("Unsupported Jingle transport " + xmlNamespace); } else { throw new IllegalArgumentException("Unsupported Jingle transport " + xmlNamespace); } } }
/** * The ProtocolProviderFactory is what actually creates instances of a ProtocolProviderService * implementation. A provider factory would register, persistently store, and remove when necessary, * ProtocolProviders. The way things are in the SIP Communicator, a user account is represented (in * a 1:1 relationship) by an AccountID and a ProtocolProvider. In other words - one would have as * many protocol providers installed in a given moment as they would user account registered through * the various services. * * @author Emil Ivov * @author Lubomir Marinov */ public abstract class ProtocolProviderFactory { /** * The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class and its instances for * logging output. */ private static final Logger logger = Logger.getLogger(ProtocolProviderFactory.class); /** Then name of a property which represents a password. */ public static final String PASSWORD = "******"; /** * The name of a property representing the name of the protocol for an ProtocolProviderFactory. */ public static final String PROTOCOL = "PROTOCOL_NAME"; /** The name of a property representing the path to protocol icons. */ public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH"; /** * The name of a property representing the path to the account icon to be used in the user * interface, when the protocol provider service is not available. */ public static final String ACCOUNT_ICON_PATH = "ACCOUNT_ICON_PATH"; /** * The name of a property which represents the AccountID of a ProtocolProvider and that, together * with a password is used to login on the protocol network.. */ public static final String USER_ID = "USER_ID"; /** The name that should be displayed to others when we are calling or writing them. */ public static final String DISPLAY_NAME = "DISPLAY_NAME"; /** The name that should be displayed to the user on call via and chat via lists. */ public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_DISPLAY_NAME"; /** The name of the property under which we store protocol AccountID-s. */ public static final String ACCOUNT_UID = "ACCOUNT_UID"; /** * The name of the property under which we store protocol the address of a protocol centric entity * (any protocol server). */ public static final String SERVER_ADDRESS = "SERVER_ADDRESS"; /** * The name of the property under which we store the number of the port where the server stored * against the SERVER_ADDRESS property is expecting connections to be made via this protocol. */ public static final String SERVER_PORT = "SERVER_PORT"; /** * The name of the property under which we store the name of the transport protocol that needs to * be used to access the server. */ public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT"; /** The name of the property under which we store protocol the address of a protocol proxy. */ public static final String PROXY_ADDRESS = "PROXY_ADDRESS"; /** * The name of the property under which we store the number of the port where the proxy stored * against the PROXY_ADDRESS property is expecting connections to be made via this protocol. */ public static final String PROXY_PORT = "PROXY_PORT"; /** * The name of the property which defines whether proxy is auto configured by the protocol by * using known methods such as specific DNS queries. */ public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG"; /** The property indicating the preferred UDP and TCP port to bind to for clear communications. */ public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME = "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT"; /** The property indicating the preferred TLS (TCP) port to bind to for secure communications. */ public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME = "net.java.sip.communicator.SIP_PREFERRED_SECURE_PORT"; /** * The name of the property under which we store the the type of the proxy stored against the * PROXY_ADDRESS property. Exact type values depend on protocols and among them are socks4, * socks5, http and possibly others. */ public static final String PROXY_TYPE = "PROXY_TYPE"; /** * The name of the property under which we store the the username for the proxy stored against the * PROXY_ADDRESS property. */ public static final String PROXY_USERNAME = "******"; /** * The name of the property under which we store the the authorization name for the proxy stored * against the PROXY_ADDRESS property. */ public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME"; /** * The name of the property under which we store the password for the proxy stored against the * PROXY_ADDRESS property. */ public static final String PROXY_PASSWORD = "******"; /** * The name of the property under which we store the name of the transport protocol that needs to * be used to access the proxy. */ public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT"; /** * The name of the property that indicates whether loose routing should be forced for all traffic * in an account, rather than routing through an outbound proxy which is the default for Jitsi. */ public static final String FORCE_PROXY_BYPASS = "******"; /** * The name of the property under which we store the user preference for a transport protocol to * use (i.e. tcp or udp). */ public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT"; /** * The name of the property under which we store whether we generate resource values or we just * use the stored one. */ public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE"; /** * The name of the property under which we store resources such as the jabber resource property. */ public static final String RESOURCE = "RESOURCE"; /** The name of the property under which we store resource priority. */ public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY"; /** The name of the property which defines that the call is encrypted by default */ public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION"; /** The name of the property that indicates the encryption protocols for this account. */ public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL"; /** * The name of the property that indicates the status (enabed or disabled) encryption protocols * for this account. */ public static final String ENCRYPTION_PROTOCOL_STATUS = "ENCRYPTION_PROTOCOL_STATUS"; /** The name of the property which defines if to include the ZRTP attribute to SIP/SDP */ public static final String DEFAULT_SIPZRTP_ATTRIBUTE = "DEFAULT_SIPZRTP_ATTRIBUTE"; /** * The name of the property which defines the ID of the client TLS certificate configuration * entry. */ public static final String CLIENT_TLS_CERTIFICATE = "CLIENT_TLS_CERTIFICATE"; /** * The name of the property under which we store the boolean value indicating if the user name * should be automatically changed if the specified name already exists. This property is meant to * be used by IRC implementations. */ public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME"; /** * The name of the property under which we store the boolean value indicating if a password is * required. Initially this property is meant to be used by IRC implementations. */ public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED"; /** The name of the property under which we store if the presence is enabled. */ public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED"; /** The name of the property under which we store if the p2p mode for SIMPLE should be forced. */ public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE"; /** * The name of the property under which we store the offline contact polling period for SIMPLE. */ public static final String POLLING_PERIOD = "POLLING_PERIOD"; /** * The name of the property under which we store the chosen default subscription expiration value * for SIMPLE. */ public static final String SUBSCRIPTION_EXPIRATION = "SUBSCRIPTION_EXPIRATION"; /** Indicates if the server address has been validated. */ public static final String SERVER_ADDRESS_VALIDATED = "SERVER_ADDRESS_VALIDATED"; /** Indicates if the server settings are over */ public static final String IS_SERVER_OVERRIDDEN = "IS_SERVER_OVERRIDDEN"; /** Indicates if the proxy address has been validated. */ public static final String PROXY_ADDRESS_VALIDATED = "PROXY_ADDRESS_VALIDATED"; /** Indicates the search strategy chosen for the DICT protocole. */ public static final String STRATEGY = "STRATEGY"; /** Indicates a protocol that would not be shown in the user interface as an account. */ public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN"; /** Indicates if the given account is the preferred account. */ public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL"; /** * The name of the property that would indicate if a given account is currently enabled or * disabled. */ public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED"; /** Indicates if ICE should be used. */ public static final String IS_USE_ICE = "ICE_ENABLED"; /** Indicates if Google ICE should be used. */ public static final String IS_USE_GOOGLE_ICE = "GTALK_ICE_ENABLED"; /** Indicates if STUN server should be automatically discovered. */ public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN"; /** Indicates if default STUN server would be used if no other STUN/TURN server are available. */ public static final String USE_DEFAULT_STUN_SERVER = "USE_DEFAULT_STUN_SERVER"; /** * The name of the boolean account property which indicates whether Jitsi VideoBridge is to be * used, if available and supported, for conference calls. */ public static final String USE_JITSI_VIDEO_BRIDGE = "USE_JITSI_VIDEO_BRIDGE"; /** * The property name prefix for all stun server properties. We generally use this prefix in * conjunction with an index which is how we store multiple servers. */ public static final String STUN_PREFIX = "STUN"; /** The base property name for address of additional STUN servers specified. */ public static final String STUN_ADDRESS = "ADDRESS"; /** The base property name for port of additional STUN servers specified. */ public static final String STUN_PORT = "PORT"; /** The base property name for username of additional STUN servers specified. */ public static final String STUN_USERNAME = "******"; /** The base property name for password of additional STUN servers specified. */ public static final String STUN_PASSWORD = "******"; /** * The base property name for the turn supported property of additional STUN servers specified. */ public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED"; /** Indicates if JingleNodes should be used with ICE. */ public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED"; /** Indicates if JingleNodes should be used with ICE. */ public static final String AUTO_DISCOVER_JINGLE_NODES = "AUTO_DISCOVER_JINGLE_NODES"; /** Indicates if JingleNodes should use buddies to search for nodes. */ public static final String JINGLE_NODES_SEARCH_BUDDIES = "JINGLE_NODES_SEARCH_BUDDIES"; /** Indicates if UPnP should be used with ICE. */ public static final String IS_USE_UPNP = "UPNP_ENABLED"; /** Indicates if we allow non-TLS connection. */ public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE"; /** Enable notifications for new voicemail messages. */ public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED"; /** * Address used to reach voicemail box, by services able to subscribe for voicemail new messages * notifications. */ public static final String VOICEMAIL_URI = "VOICEMAIL_URI"; /** Address used to call to hear your messages stored on the server for your voicemail. */ public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI"; /** Indicates if calling is disabled for a certain account. */ public static final String IS_CALLING_DISABLED_FOR_ACCOUNT = "CALLING_DISABLED"; /** Indicates if desktop streaming/sharing is disabled for a certain account. */ public static final String IS_DESKTOP_STREAMING_DISABLED = "DESKTOP_STREAMING_DISABLED"; /** The sms default server address. */ public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS"; /** Keep-alive method used by the protocol. */ public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD"; /** The interval for keep-alives if any. */ public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL"; /** The minimal DTMF tone duration. */ public static final String DTMF_MINIMAL_TONE_DURATION = "DTMF_MINIMAL_TONE_DURATION"; /** Paranoia mode when turned on requires all calls to be secure and indicated as such. */ public static final String MODE_PARANOIA = "MODE_PARANOIA"; /** The name of the "override encodings" property */ public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS"; /** The prefix used to store account encoding properties */ public static final String ENCODING_PROP_PREFIX = "Encodings"; /** * The <code>BundleContext</code> containing (or to contain) the service registration of this * factory. */ private final BundleContext bundleContext; /** * The name of the protocol this factory registers its <code>ProtocolProviderService</code>s with * and to be placed in the properties of the accounts created by this factory. */ private final String protocolName; /** * The table that we store our accounts in. * * <p>TODO Synchronize the access to the field which may in turn be better achieved by also hiding * it from protected into private access. */ protected final Hashtable<AccountID, ServiceRegistration> registeredAccounts = new Hashtable<AccountID, ServiceRegistration>(); /** * The name of the property that indicates the AVP type. * * <ul> * <li>{@link #SAVP_OFF} * <li>{@link #SAVP_MANDATORY} * <li>{@link #SAVP_OPTIONAL} * </ul> */ public static final String SAVP_OPTION = "SAVP_OPTION"; /** Always use RTP/AVP */ public static final int SAVP_OFF = 0; /** Always use RTP/SAVP */ public static final int SAVP_MANDATORY = 1; /** Sends two media description, with RTP/SAVP being first. */ public static final int SAVP_OPTIONAL = 2; /** * The name of the property that defines the enabled SDES cipher suites. Enabled suites are listed * as CSV by their RFC name. */ public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES"; /** * Creates a new <tt>ProtocolProviderFactory</tt>. * * @param bundleContext the bundle context reference of the service * @param protocolName the name of the protocol */ protected ProtocolProviderFactory(BundleContext bundleContext, String protocolName) { this.bundleContext = bundleContext; this.protocolName = protocolName; } /** * Gets the <code>BundleContext</code> containing (or to contain) the service registration of this * factory. * * @return the <code>BundleContext</code> containing (or to contain) the service registration of * this factory */ public BundleContext getBundleContext() { return bundleContext; } /** * Initializes and creates an account corresponding to the specified accountProperties and * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter. Note * that account registration is persistent and accounts that are registered during a particular * sip-communicator session would be automatically reloaded during all following sessions until * they are removed through the removeAccount method. * * @param userID the user identifier uniquely representing the newly created account within the * protocol namespace. * @param accountProperties a set of protocol (or implementation) specific properties defining the * new account. * @return the AccountID of the newly created account. * @throws java.lang.IllegalArgumentException if userID does not correspond to an identifier in * the context of the underlying protocol or if accountProperties does not contain a complete * set of account installation properties. * @throws java.lang.IllegalStateException if the account has already been installed. * @throws java.lang.NullPointerException if any of the arguments is null. */ public abstract AccountID installAccount(String userID, Map<String, String> accountProperties) throws IllegalArgumentException, IllegalStateException, NullPointerException; /** * Modifies the account corresponding to the specified accountID. This method is meant to be used * to change properties of already existing accounts. Note that if the given accountID doesn't * correspond to any registered account this method would do nothing. * * @param protocolProvider the protocol provider service corresponding to the modified account. * @param accountProperties a set of protocol (or implementation) specific properties defining the * new account. * @throws java.lang.NullPointerException if any of the arguments is null. */ public abstract void modifyAccount( ProtocolProviderService protocolProvider, Map<String, String> accountProperties) throws NullPointerException; /** * Returns a copy of the list containing the <tt>AccountID</tt>s of all accounts currently * registered in this protocol provider. * * @return a copy of the list containing the <tt>AccountID</tt>s of all accounts currently * registered in this protocol provider. */ public ArrayList<AccountID> getRegisteredAccounts() { synchronized (registeredAccounts) { return new ArrayList<AccountID>(registeredAccounts.keySet()); } } /** * Returns the ServiceReference for the protocol provider corresponding to the specified accountID * or null if the accountID is unknown. * * @param accountID the accountID of the protocol provider we'd like to get * @return a ServiceReference object to the protocol provider with the specified account id and * null if the account id is unknown to the provider factory. */ public ServiceReference getProviderForAccount(AccountID accountID) { ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.get(accountID); } return (registration == null) ? null : registration.getReference(); } /** * Removes the specified account from the list of accounts that this provider factory is handling. * If the specified accountID is unknown to the ProtocolProviderFactory, the call has no effect * and false is returned. This method is persistent in nature and once called the account * corresponding to the specified ID will not be loaded during future runs of the project. * * @param accountID the ID of the account to remove. * @return true if an account with the specified ID existed and was removed and false otherwise. */ public boolean uninstallAccount(AccountID accountID) { // Unregister the protocol provider. ServiceReference serRef = getProviderForAccount(accountID); boolean wasAccountExisting = false; // If the protocol provider service is registered, first unregister the // service. if (serRef != null) { BundleContext bundleContext = getBundleContext(); ProtocolProviderService protocolProvider = (ProtocolProviderService) bundleContext.getService(serRef); try { protocolProvider.unregister(); } catch (OperationFailedException ex) { logger.error( "Failed to unregister protocol provider for account: " + accountID + " caused by: " + ex); } } ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.remove(accountID); } // first remove the stored account so when PP is unregistered we can // distinguish between deleted or just disabled account wasAccountExisting = removeStoredAccount(accountID); if (registration != null) { // Kill the service. registration.unregister(); } return wasAccountExisting; } /** * The method stores the specified account in the configuration service under the package name of * the source factory. The restore and remove account methods are to be used to obtain access to * and control the stored accounts. * * <p>In order to store all account properties, the method would create an entry in the * configuration service corresponding (beginning with) the <tt>sourceFactory</tt>'s package name * and add to it a unique identifier (e.g. the current miliseconds.) * * @param accountID the AccountID corresponding to the account that we would like to store. */ protected void storeAccount(AccountID accountID) { this.storeAccount(accountID, true); } /** * The method stores the specified account in the configuration service under the package name of * the source factory. The restore and remove account methods are to be used to obtain access to * and control the stored accounts. * * <p>In order to store all account properties, the method would create an entry in the * configuration service corresponding (beginning with) the <tt>sourceFactory</tt>'s package name * and add to it a unique identifier (e.g. the current miliseconds.) * * @param accountID the AccountID corresponding to the account that we would like to store. * @param isModification if <tt>false</tt> there must be no such already loaded account, it * <tt>true</tt> ist modification of an existing account. Usually we use this method with * <tt>false</tt> in method installAccount and with <tt>true</tt> or the overridden method in * method modifyAccount. */ protected void storeAccount(AccountID accountID, boolean isModification) { if (!isModification && getAccountManager().getStoredAccounts().contains(accountID)) { throw new IllegalStateException( "An account for id " + accountID.getUserID() + " was already loaded!"); } try { getAccountManager().storeAccount(this, accountID); } catch (OperationFailedException ofex) { throw new UndeclaredThrowableException(ofex); } } /** * Saves the password for the specified account after scrambling it a bit so that it is not * visible from first sight. (The method remains highly insecure). * * @param accountID the AccountID for the account whose password we're storing * @param password the password itself * @throws IllegalArgumentException if no account corresponding to <code>accountID</code> has been * previously stored */ public void storePassword(AccountID accountID, String password) throws IllegalArgumentException { try { storePassword(getBundleContext(), accountID, password); } catch (OperationFailedException ofex) { throw new UndeclaredThrowableException(ofex); } } /** * Saves the password for the specified account after scrambling it a bit so that it is not * visible from first sight (Method remains highly insecure). * * <p>TODO Delegate the implementation to {@link AccountManager} because it knows the format in * which the password (among the other account properties) is to be saved. * * @param bundleContext a currently valid bundle context. * @param accountID the <tt>AccountID</tt> of the account whose password is to be stored * @param password the password to be stored * @throws IllegalArgumentException if no account corresponding to <tt>accountID</tt> has been * previously stored. * @throws OperationFailedException if anything goes wrong while storing the specified * <tt>password</tt> */ protected void storePassword(BundleContext bundleContext, AccountID accountID, String password) throws IllegalArgumentException, OperationFailedException { String accountPrefix = findAccountPrefix(bundleContext, accountID, getFactoryImplPackageName()); if (accountPrefix == null) { throw new IllegalArgumentException( "No previous records found for account ID: " + accountID.getAccountUniqueID() + " in package" + getFactoryImplPackageName()); } CredentialsStorageService credentialsStorage = ServiceUtils.getService(bundleContext, CredentialsStorageService.class); if (!credentialsStorage.storePassword(accountPrefix, password)) { throw new OperationFailedException( "CredentialsStorageService failed to storePassword", OperationFailedException.GENERAL_ERROR); } } /** * Returns the password last saved for the specified account. * * @param accountID the AccountID for the account whose password we're looking for * @return a String containing the password for the specified accountID */ public String loadPassword(AccountID accountID) { return loadPassword(getBundleContext(), accountID); } /** * Returns the password last saved for the specified account. * * <p>TODO Delegate the implementation to {@link AccountManager} because it knows the format in * which the password (among the other account properties) was saved. * * @param bundleContext a currently valid bundle context. * @param accountID the AccountID for the account whose password we're looking for.. * @return a String containing the password for the specified accountID. */ protected String loadPassword(BundleContext bundleContext, AccountID accountID) { String accountPrefix = findAccountPrefix(bundleContext, accountID, getFactoryImplPackageName()); if (accountPrefix == null) return null; CredentialsStorageService credentialsStorage = ServiceUtils.getService(bundleContext, CredentialsStorageService.class); return credentialsStorage.loadPassword(accountPrefix); } /** * Initializes and creates an account corresponding to the specified accountProperties and * registers the resulting ProtocolProvider in the <tt>context</tt> BundleContext parameter. This * method has a persistent effect. Once created the resulting account will remain installed until * removed through the uninstallAccount method. * * @param accountProperties a set of protocol (or implementation) specific properties defining the * new account. * @return the AccountID of the newly loaded account */ public AccountID loadAccount(Map<String, String> accountProperties) { AccountID accountID = createAccount(accountProperties); loadAccount(accountID); return accountID; } /** * Creates a protocol provider for the given <tt>accountID</tt> and registers it in the bundle * context. This method has a persistent effect. Once created the resulting account will remain * installed until removed through the uninstallAccount method. * * @param accountID the account identifier * @return <tt>true</tt> if the account with the given <tt>accountID</tt> is successfully loaded, * otherwise returns <tt>false</tt> */ public boolean loadAccount(AccountID accountID) { // Need to obtain the original user id property, instead of calling // accountID.getUserID(), because this method could return a modified // version of the user id property. String userID = accountID.getAccountPropertyString(ProtocolProviderFactory.USER_ID); ProtocolProviderService service = createService(userID, accountID); Dictionary<String, String> properties = new Hashtable<String, String>(); properties.put(PROTOCOL, protocolName); properties.put(USER_ID, userID); ServiceRegistration serviceRegistration = bundleContext.registerService(ProtocolProviderService.class.getName(), service, properties); if (serviceRegistration == null) return false; else { synchronized (registeredAccounts) { registeredAccounts.put(accountID, serviceRegistration); } return true; } } /** * Unloads the account corresponding to the given <tt>accountID</tt>. Unregisters the * corresponding protocol provider, but keeps the account in contrast to the uninstallAccount * method. * * @param accountID the account identifier * @return true if an account with the specified ID existed and was unloaded and false otherwise. */ public boolean unloadAccount(AccountID accountID) { // Unregister the protocol provider. ServiceReference serRef = getProviderForAccount(accountID); if (serRef == null) { return false; } BundleContext bundleContext = getBundleContext(); ProtocolProviderService protocolProvider = (ProtocolProviderService) bundleContext.getService(serRef); try { protocolProvider.unregister(); } catch (OperationFailedException ex) { logger.error( "Failed to unregister protocol provider for account : " + accountID + " caused by: " + ex); } ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.remove(accountID); } if (registration == null) { return false; } // Kill the service. registration.unregister(); return true; } /** * 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); } /** * Creates a new <code>AccountID</code> instance with a specific user ID to represent a given set * of account properties. * * <p>The method is a pure factory allowing implementers to specify the runtime type of the * created <code>AccountID</code> and customize the instance. The returned <code>AccountID</code> * will later be associated with a <code>ProtocolProviderService</code> by the caller (e.g. using * {@link #createService(String, AccountID)}). * * @param userID the user ID of the new instance * @param accountProperties the set of properties to be represented by the new instance * @return a new <code>AccountID</code> instance with the specified user ID representing the given * set of account properties */ protected abstract AccountID createAccountID( String userID, Map<String, String> accountProperties); /** * Gets the name of the protocol this factory registers its <code>ProtocolProviderService</code>s * with and to be placed in the properties of the accounts created by this factory. * * @return the name of the protocol this factory registers its <code>ProtocolProviderService * </code>s with and to be placed in the properties of the accounts created by this factory */ public String getProtocolName() { return protocolName; } /** * Initializes a new <code>ProtocolProviderService</code> instance with a specific user ID to * represent a specific <code>AccountID</code>. * * <p>The method is a pure factory allowing implementers to specify the runtime type of the * created <code>ProtocolProviderService</code> and customize the instance. The caller will later * register the returned service with the <code>BundleContext</code> of this factory. * * @param userID the user ID to initialize the new instance with * @param accountID the <code>AccountID</code> to be represented by the new instance * @return a new <code>ProtocolProviderService</code> instance with the specific user ID * representing the specified <code>AccountID</code> */ protected abstract ProtocolProviderService createService(String userID, AccountID accountID); /** * Removes the account with <tt>accountID</tt> from the set of accounts that are persistently * stored inside the configuration service. * * @param accountID the AccountID of the account to remove. * @return true if an account has been removed and false otherwise. */ protected boolean removeStoredAccount(AccountID accountID) { return getAccountManager().removeStoredAccount(this, accountID); } /** * Returns the prefix for all persistently stored properties of the account with the specified id. * * @param bundleContext a currently valid bundle context. * @param accountID the AccountID of the account whose properties we're looking for. * @param sourcePackageName a String containing the package name of the concrete factory class * that extends us. * @return a String indicating the ConfigurationService property name prefix under which all * account properties are stored or null if no account corresponding to the specified id was * found. */ public static String findAccountPrefix( BundleContext bundleContext, AccountID accountID, String sourcePackageName) { ServiceReference confReference = bundleContext.getServiceReference(ConfigurationService.class.getName()); ConfigurationService configurationService = (ConfigurationService) bundleContext.getService(confReference); // first retrieve all accounts that we've registered List<String> storedAccounts = configurationService.getPropertyNamesByPrefix(sourcePackageName, true); // find an account with the corresponding id. for (String accountRootPropertyName : storedAccounts) { // unregister the account in the configuration service. // all the properties must have been registered in the following // hierarchy: // net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME String accountUID = configurationService.getString( accountRootPropertyName // node id + "." + ACCOUNT_UID); // propname if (accountID.getAccountUniqueID().equals(accountUID)) { return accountRootPropertyName; } } return null; } /** * Returns the name of the package that we're currently running in (i.e. the name of the package * containing the proto factory that extends us). * * @return a String containing the package name of the concrete factory class that extends us. */ private String getFactoryImplPackageName() { String className = getClass().getName(); return className.substring(0, className.lastIndexOf('.')); } /** Prepares the factory for bundle shutdown. */ public void stop() { if (logger.isTraceEnabled()) logger.trace("Preparing to stop all protocol providers of" + this); synchronized (registeredAccounts) { for (Enumeration<ServiceRegistration> registrations = registeredAccounts.elements(); registrations.hasMoreElements(); ) { ServiceRegistration reg = registrations.nextElement(); stop(reg); reg.unregister(); } registeredAccounts.clear(); } } /** * Shuts down the <code>ProtocolProviderService</code> representing an account registered with * this factory. * * @param registeredAccount the <code>ServiceRegistration</code> of the <code> * ProtocolProviderService</code> representing an account registered with this factory */ protected void stop(ServiceRegistration registeredAccount) { ProtocolProviderService protocolProviderService = (ProtocolProviderService) getBundleContext().getService(registeredAccount.getReference()); protocolProviderService.shutdown(); } /** * Get the <tt>AccountManager</tt> of the protocol. * * @return <tt>AccountManager</tt> of the protocol */ private AccountManager getAccountManager() { BundleContext bundleContext = getBundleContext(); ServiceReference serviceReference = bundleContext.getServiceReference(AccountManager.class.getName()); return (AccountManager) bundleContext.getService(serviceReference); } }
/** * Creates an instance of <tt>ShowPreviewDialog</tt> * * @param chatPanel The <tt>ChatConversationPanel</tt> that is associated with this dialog. */ ShowPreviewDialog(final ChatConversationPanel chatPanel) { this.chatPanel = chatPanel; this.setTitle( GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_DIALOG_TITLE")); okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK")); cancelButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL")); JPanel mainPanel = new TransparentPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // mainPanel.setPreferredSize(new Dimension(200, 150)); this.getContentPane().add(mainPanel); JTextPane descriptionMsg = new JTextPane(); descriptionMsg.setEditable(false); descriptionMsg.setOpaque(false); descriptionMsg.setText( GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_WARNING_DESCRIPTION")); Icon warningIcon = null; try { warningIcon = new ImageIcon( ImageIO.read( GuiActivator.getResources().getImageURL("service.gui.icons.WARNING_ICON"))); } catch (IOException e) { logger.debug("failed to load the warning icon"); } JLabel warningSign = new JLabel(warningIcon); JPanel warningPanel = new TransparentPanel(); warningPanel.setLayout(new BoxLayout(warningPanel, BoxLayout.X_AXIS)); warningPanel.add(warningSign); warningPanel.add(Box.createHorizontalStrut(10)); warningPanel.add(descriptionMsg); enableReplacement = new JCheckBox( GuiActivator.getResources() .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS")); enableReplacement.setOpaque(false); enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true)); enableReplacementProposal = new JCheckBox( GuiActivator.getResources() .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL")); enableReplacementProposal.setOpaque(false); JPanel checkBoxPanel = new TransparentPanel(); checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.Y_AXIS)); checkBoxPanel.add(enableReplacement); checkBoxPanel.add(enableReplacementProposal); JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER)); buttonsPanel.add(okButton); buttonsPanel.add(cancelButton); mainPanel.add(warningPanel); mainPanel.add(Box.createVerticalStrut(10)); mainPanel.add(checkBoxPanel); mainPanel.add(buttonsPanel); okButton.addActionListener(this); cancelButton.addActionListener(this); this.setPreferredSize(new Dimension(390, 230)); }
public class ShowPreviewDialog extends SIPCommDialog implements ActionListener, ChatLinkClickedListener { /** Serial version UID. */ private static final long serialVersionUID = 1L; /** * The <tt>Logger</tt> used by the <tt>ShowPreviewDialog</tt> class and its instances for logging * output. */ private static final Logger logger = Logger.getLogger(ShowPreviewDialog.class); ConfigurationService cfg = GuiActivator.getConfigurationService(); /** The Ok button. */ private final JButton okButton; /** The cancel button. */ private final JButton cancelButton; /** Checkbox that indicates whether or not to show this dialog next time. */ private final JCheckBox enableReplacementProposal; /** Checkbox that indicates whether or not to show previews automatically */ private final JCheckBox enableReplacement; /** The <tt>ChatConversationPanel</tt> that this dialog is associated with. */ private final ChatConversationPanel chatPanel; /** Mapping between messageID and the string representation of the chat message. */ private Map<String, String> msgIDToChatString = new ConcurrentHashMap<String, String>(); /** * Mapping between the pair (messageID, link position) and the actual link in the string * representation of the chat message. */ private Map<String, String> msgIDandPositionToLink = new ConcurrentHashMap<String, String>(); /** * Mapping between link and replacement for this link that is acquired from it's corresponding * <tt>ReplacementService</tt>. */ private Map<String, String> linkToReplacement = new ConcurrentHashMap<String, String>(); /** The id of the message that is currently associated with this dialog. */ private String currentMessageID = ""; /** The position of the link in the current message. */ private String currentLinkPosition = ""; /** * Creates an instance of <tt>ShowPreviewDialog</tt> * * @param chatPanel The <tt>ChatConversationPanel</tt> that is associated with this dialog. */ ShowPreviewDialog(final ChatConversationPanel chatPanel) { this.chatPanel = chatPanel; this.setTitle( GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_DIALOG_TITLE")); okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK")); cancelButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL")); JPanel mainPanel = new TransparentPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // mainPanel.setPreferredSize(new Dimension(200, 150)); this.getContentPane().add(mainPanel); JTextPane descriptionMsg = new JTextPane(); descriptionMsg.setEditable(false); descriptionMsg.setOpaque(false); descriptionMsg.setText( GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_WARNING_DESCRIPTION")); Icon warningIcon = null; try { warningIcon = new ImageIcon( ImageIO.read( GuiActivator.getResources().getImageURL("service.gui.icons.WARNING_ICON"))); } catch (IOException e) { logger.debug("failed to load the warning icon"); } JLabel warningSign = new JLabel(warningIcon); JPanel warningPanel = new TransparentPanel(); warningPanel.setLayout(new BoxLayout(warningPanel, BoxLayout.X_AXIS)); warningPanel.add(warningSign); warningPanel.add(Box.createHorizontalStrut(10)); warningPanel.add(descriptionMsg); enableReplacement = new JCheckBox( GuiActivator.getResources() .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS")); enableReplacement.setOpaque(false); enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true)); enableReplacementProposal = new JCheckBox( GuiActivator.getResources() .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL")); enableReplacementProposal.setOpaque(false); JPanel checkBoxPanel = new TransparentPanel(); checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.Y_AXIS)); checkBoxPanel.add(enableReplacement); checkBoxPanel.add(enableReplacementProposal); JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER)); buttonsPanel.add(okButton); buttonsPanel.add(cancelButton); mainPanel.add(warningPanel); mainPanel.add(Box.createVerticalStrut(10)); mainPanel.add(checkBoxPanel); mainPanel.add(buttonsPanel); okButton.addActionListener(this); cancelButton.addActionListener(this); this.setPreferredSize(new Dimension(390, 230)); } @Override public void actionPerformed(ActionEvent arg0) { if (arg0.getSource().equals(okButton)) { cfg.setProperty(ReplacementProperty.REPLACEMENT_ENABLE, enableReplacement.isSelected()); cfg.setProperty( ReplacementProperty.REPLACEMENT_PROPOSAL, enableReplacementProposal.isSelected()); SwingWorker worker = new SwingWorker() { /** * Called on the event dispatching thread (not on the worker thread) after the <code> * construct</code> method has returned. */ @Override public void finished() { String newChatString = (String) get(); if (newChatString != null) { try { Element elem = chatPanel.document.getElement(currentMessageID); chatPanel.document.setOuterHTML(elem, newChatString); msgIDToChatString.put(currentMessageID, newChatString); } catch (BadLocationException ex) { logger.error("Could not replace chat message", ex); } catch (IOException ex) { logger.error("Could not replace chat message", ex); } } } @Override protected Object construct() throws Exception { String newChatString = msgIDToChatString.get(currentMessageID); try { String originalLink = msgIDandPositionToLink.get(currentMessageID + "#" + currentLinkPosition); String replacementLink = linkToReplacement.get(originalLink); String replacement; DirectImageReplacementService source = GuiActivator.getDirectImageReplacementSource(); if (originalLink.equals(replacementLink) && (!source.isDirectImage(originalLink) || source.getImageSize(originalLink) == -1)) { replacement = originalLink; } else { replacement = "<IMG HEIGHT=\"90\" WIDTH=\"120\" SRC=\"" + replacementLink + "\" BORDER=\"0\" ALT=\"" + originalLink + "\"></IMG>"; } String old = originalLink + "</A> <A href=\"jitsi://" + ShowPreviewDialog.this.getClass().getName() + "/SHOWPREVIEW?" + currentMessageID + "#" + currentLinkPosition + "\">" + GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW"); newChatString = newChatString.replace(old, replacement); } catch (Exception ex) { logger.error("Could not replace chat message", ex); } return newChatString; } }; worker.start(); this.setVisible(false); } else if (arg0.getSource().equals(cancelButton)) { this.setVisible(false); } } @Override public void chatLinkClicked(URI url) { String action = url.getPath(); if (action.equals("/SHOWPREVIEW")) { enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true)); enableReplacementProposal.setSelected( cfg.getBoolean(ReplacementProperty.REPLACEMENT_PROPOSAL, true)); currentMessageID = url.getQuery(); currentLinkPosition = url.getFragment(); this.setVisible(true); this.setLocationRelativeTo(chatPanel); } } /** * Returns mapping between messageID and the string representation of the chat message. * * @return mapping between messageID and chat string. */ Map<String, String> getMsgIDToChatString() { return msgIDToChatString; } /** * Returns mapping between the pair (messageID, link position) and the actual link in the string * representation of the chat message. * * @return mapping between (messageID, linkPosition) and link. */ Map<String, String> getMsgIDandPositionToLink() { return msgIDandPositionToLink; } /** * Returns mapping between link and replacement for this link that was acquired from it's * corresponding <tt>ReplacementService</tt>. * * @return mapping between link and it's corresponding replacement. */ Map<String, String> getLinkToReplacement() { return linkToReplacement; } }
/** * Implements the {@link ReplacementService} to provide previews for direct image links. * * @author Purvesh Sahoo * @author Marin Dzhigarov */ public class ReplacementServiceDirectImageImpl implements DirectImageReplacementService { /** The logger for this class. */ private static final Logger logger = Logger.getLogger(ReplacementServiceDirectImageImpl.class); /** The regex used to match the link in the message. */ public static final String URL_PATTERN = "https?\\:\\/\\/(www\\.)*.*\\.(?:jpg|png|gif)"; /** Configuration label shown in the config form. */ public static final String DIRECT_IMAGE_CONFIG_LABEL = "Direct Image Link"; /** Source name; also used as property label. */ public static final String SOURCE_NAME = "DIRECTIMAGE"; /** Maximum allowed size of the image in bytes. The default size is 2MB. */ private long imgMaxSize = 2097152; /** Configuration property name for maximum allowed size of the image in bytes. */ private static final String MAX_IMG_SIZE = "net.java.sip.communicator.impl.replacement.directimage.MAX_IMG_SIZE"; /** Constructor for <tt>ReplacementServiceDirectImageImpl</tt>. */ public ReplacementServiceDirectImageImpl() { setMaxImgSizeFromConf(); logger.trace("Creating a Direct Image Link Source."); } /** * Gets the max allowed size value in bytes from Configuration service and sets the value to * <tt>imgMaxSize</tt> if succeed. If the configuration property isn't available or the value * can't be parsed correctly the value of <tt>imgMaxSize</tt> isn't changed. */ private void setMaxImgSizeFromConf() { ConfigurationService configService = DirectImageActivator.getConfigService(); if (configService != null) { String confImgSizeStr = (String) configService.getProperty(MAX_IMG_SIZE); try { if (confImgSizeStr != null) { imgMaxSize = Long.parseLong(confImgSizeStr); } else { configService.setProperty(MAX_IMG_SIZE, imgMaxSize); } } catch (NumberFormatException e) { if (logger.isDebugEnabled()) logger.debug( "Failed to parse max image size: " + confImgSizeStr + ". Going for default."); } } } /** * Returns the thumbnail URL of the image link provided. * * @param sourceString the original image link. * @return the thumbnail image link; the original link in case of no match. */ public String getReplacement(String sourceString) { return sourceString; } /** * Returns the source name * * @return the source name */ public String getSourceName() { return SOURCE_NAME; } /** * Returns the pattern of the source * * @return the source pattern */ public String getPattern() { return URL_PATTERN; } @Override /** * Returns the size of the image in bytes. * * @param sourceString the image link. * @return the file size in bytes of the image link provided; -1 if the size isn't available or * exceeds the max allowed image size. */ public int getImageSize(String sourceString) { int length = -1; try { URL url = new URL(sourceString); String protocol = url.getProtocol(); if (protocol.equals("http") || protocol.equals("https")) { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); length = connection.getContentLength(); connection.disconnect(); } else if (protocol.equals("ftp")) { FTPUtils ftp = new FTPUtils(sourceString); length = ftp.getSize(); ftp.disconnect(); } if (length > imgMaxSize) { length = -1; } } catch (Exception e) { logger.debug("Failed to get the length of the image in bytes", e); } return length; } /** * Returns true if the content type of the resource pointed by sourceString is an image. * * @param sourceString the original image link. * @return true if the content type of the resource pointed by sourceString is an image. */ @Override public boolean isDirectImage(String sourceString) { boolean isDirectImage = false; try { URL url = new URL(sourceString); String protocol = url.getProtocol(); if (protocol.equals("http") || protocol.equals("https")) { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); isDirectImage = connection.getContentType().contains("image"); connection.disconnect(); } else if (protocol.equals("ftp")) { if (sourceString.endsWith(".png") || sourceString.endsWith(".jpg") || sourceString.endsWith(".gif")) { isDirectImage = true; } } } catch (Exception e) { logger.debug("Failed to retrieve content type information for" + sourceString, e); } return isDirectImage; } }
private void runOnDtlsTransport(StreamConnector connector) throws IOException { DtlsControlImpl dtlsControl = (DtlsControlImpl) getTransportManager().getDtlsControl(this); DtlsTransformEngine engine = dtlsControl.getTransformEngine(); final DtlsPacketTransformer transformer = (DtlsPacketTransformer) engine.getRTPTransformer(); byte[] receiveBuffer = new byte[SCTP_BUFFER_SIZE]; if (LOG_SCTP_PACKETS) { System.setProperty( ConfigurationService.PNAME_SC_HOME_DIR_LOCATION, System.getProperty("java.io.tmpdir")); System.setProperty( ConfigurationService.PNAME_SC_HOME_DIR_NAME, SctpConnection.class.getName()); } synchronized (this) { // FIXME local SCTP port is hardcoded in bridge offer SDP (Jitsi // Meet) sctpSocket = Sctp.createSocket(5000); assocIsUp = false; acceptedIncomingConnection = false; } // Implement output network link for SCTP stack on DTLS transport sctpSocket.setLink( new NetworkLink() { @Override public void onConnOut(SctpSocket s, byte[] packet) throws IOException { if (LOG_SCTP_PACKETS) { LibJitsi.getPacketLoggingService() .logPacket( PacketLoggingService.ProtocolName.ICE4J, new byte[] {0, 0, 0, (byte) debugId}, 5000, new byte[] {0, 0, 0, (byte) (debugId + 1)}, remoteSctpPort, PacketLoggingService.TransportName.UDP, true, packet); } // Send through DTLS transport transformer.sendApplicationData(packet, 0, packet.length); } }); if (logger.isDebugEnabled()) { logger.debug("Connecting SCTP to port: " + remoteSctpPort + " to " + getEndpoint().getID()); } sctpSocket.setNotificationListener(this); sctpSocket.listen(); // FIXME manage threads threadPool.execute( new Runnable() { @Override public void run() { SctpSocket sctpSocket = null; try { // sctpSocket is set to null on close sctpSocket = SctpConnection.this.sctpSocket; while (sctpSocket != null) { if (sctpSocket.accept()) { acceptedIncomingConnection = true; break; } Thread.sleep(100); sctpSocket = SctpConnection.this.sctpSocket; } if (isReady()) { notifySctpConnectionReady(); } } catch (Exception e) { logger.error("Error accepting SCTP connection", e); } if (sctpSocket == null && logger.isInfoEnabled()) { logger.info( "SctpConnection " + getID() + " closed" + " before SctpSocket accept()-ed."); } } }); // Notify that from now on SCTP connection is considered functional sctpSocket.setDataCallback(this); // Setup iceSocket DatagramSocket datagramSocket = connector.getDataSocket(); if (datagramSocket != null) { this.iceSocket = new IceUdpSocketWrapper(datagramSocket); } else { this.iceSocket = new IceTcpSocketWrapper(connector.getDataTCPSocket()); } DatagramPacket rcvPacket = new DatagramPacket(receiveBuffer, 0, receiveBuffer.length); // Receive loop, breaks when SCTP socket is closed try { do { iceSocket.receive(rcvPacket); RawPacket raw = new RawPacket(rcvPacket.getData(), rcvPacket.getOffset(), rcvPacket.getLength()); raw = transformer.reverseTransform(raw); // Check for app data if (raw == null) continue; if (LOG_SCTP_PACKETS) { LibJitsi.getPacketLoggingService() .logPacket( PacketLoggingService.ProtocolName.ICE4J, new byte[] {0, 0, 0, (byte) (debugId + 1)}, remoteSctpPort, new byte[] {0, 0, 0, (byte) debugId}, 5000, PacketLoggingService.TransportName.UDP, false, raw.getBuffer(), raw.getOffset(), raw.getLength()); } // Pass network packet to SCTP stack sctpSocket.onConnIn(raw.getBuffer(), raw.getOffset(), raw.getLength()); } while (true); } finally { // Eventually, close the socket although it should happen from // expire(). synchronized (this) { assocIsUp = false; acceptedIncomingConnection = false; if (sctpSocket != null) { sctpSocket.close(); sctpSocket = null; } } } }
/** * 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); } }
/** Constructor for <tt>ReplacementServiceDirectImageImpl</tt>. */ public ReplacementServiceDirectImageImpl() { setMaxImgSizeFromConf(); logger.trace("Creating a Direct Image Link Source."); }
/** * Activator for the swing notification service. * * @author Symphorien Wanko */ public class SwingNotificationActivator implements BundleActivator { /** The bundle context in which we started */ public static BundleContext bundleContext; /** A reference to the configuration service. */ private static ConfigurationService configService; /** Logger for this class. */ private static final Logger logger = Logger.getLogger(SwingNotificationActivator.class); /** A reference to the resource management service. */ private static ResourceManagementService resourcesService; /** * Start the swing notification service * * @param bc * @throws java.lang.Exception */ public void start(BundleContext bc) throws Exception { if (logger.isInfoEnabled()) logger.info("Swing Notification ...[ STARTING ]"); bundleContext = bc; PopupMessageHandler handler = null; handler = new PopupMessageHandlerSwingImpl(); getConfigurationService(); bc.registerService(PopupMessageHandler.class.getName(), handler, null); if (logger.isInfoEnabled()) logger.info("Swing Notification ...[REGISTERED]"); } public void stop(BundleContext arg0) throws Exception {} /** * Returns the <tt>ConfigurationService</tt> obtained from the bundle context. * * @return the <tt>ConfigurationService</tt> obtained from the bundle context */ public static ConfigurationService getConfigurationService() { if (configService == null) { ServiceReference configReference = bundleContext.getServiceReference(ConfigurationService.class.getName()); configService = (ConfigurationService) bundleContext.getService(configReference); } return configService; } /** * Returns the <tt>ResourceManagementService</tt> obtained from the bundle context. * * @return the <tt>ResourceManagementService</tt> obtained from the bundle context */ public static ResourceManagementService getResources() { if (resourcesService == null) resourcesService = ResourceManagementServiceUtils.getService(bundleContext); return resourcesService; } }