예제 #1
0
  /**
   * 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;
  }
예제 #2
0
  /** 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;
    }
  }
예제 #3
0
  /**
   * 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]");
  }
예제 #4
0
 /** 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;
  }
예제 #6
0
  /**
   * 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);
  }
예제 #7
0
  /**
   * 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.");
      }
    }
  }
예제 #10
0
  /**
   * 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");
    }
  }
예제 #11
0
  /**
   * 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;
    }
  }
예제 #12
0
  /**
   * {@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);
    }
  }
예제 #13
0
 /**
  * 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;
   }
 }
예제 #14
0
  /**
   * 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;
  }
예제 #15
0
  /**
   * 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;
 }
예제 #18
0
  /**
   * 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);
      }
    }
  }
예제 #19
0
  /** 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;
      }
    }
  }
예제 #20
0
  /**
   * 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;
  }
예제 #22
0
/**
 * 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);
  }
}
예제 #24
0
  /**
   * 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));
  }
예제 #25
0
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;
  }
}
예제 #27
0
  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;
        }
      }
    }
  }
예제 #28
0
  /**
   * 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.");
 }
예제 #30
0
/**
 * 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;
  }
}