/**
   * Dispatches the event received from a JAIN-SIP <tt>SipProvider</tt> to one of our "candidate
   * recipient" listeners.
   *
   * @param event the event received for a <tt>SipProvider</tt>.
   */
  public void processTimeout(TimeoutEvent event) {
    try {
      Transaction transaction;
      if (event.isServerTransaction()) {
        transaction = event.getServerTransaction();
      } else {
        transaction = event.getClientTransaction();
      }

      ProtocolProviderServiceSipImpl recipient = getServiceData(transaction);
      if (recipient == null) {
        logger.error(
            "We received a timeout which wasn't "
                + "marked, please report this to "
                + "*****@*****.**");
      } else {
        recipient.processTimeout(event);
      }
    } catch (Throwable exc) {
      // any exception thrown within our code should be caught here
      // so that we could log it rather than interrupt stack activity with
      // it.
      this.logApplicationException(DialogTerminatedEvent.class, exc);
    }
  }
  /**
   * Populates a specific <tt>Request</tt> instance with the headers common to dialog-creating
   * <tt>Request</tt>s and ones sent inside existing dialogs and specific to the general event
   * package subscription functionality that this instance and a specific <tt>Subscription</tt>
   * represent.
   *
   * @param req the <tt>Request</tt> instance to be populated with common headers and ones specific
   *     to the event package of a specific <tt>Subscription</tt>
   * @param subscription the <tt>Subscription</tt> which is to be described in the specified
   *     <tt>Request</tt> i.e. its properties are to be used to populate the specified
   *     <tt>Request</tt>
   * @param expires the subscription duration to be set into the Expires header of the specified
   *     SUBSCRIBE <tt>Request</tt>
   * @throws OperationFailedException if we fail parsing or populating the subscription request.
   */
  protected void populateSubscribeRequest(Request req, Subscription subscription, int expires)
      throws OperationFailedException {
    HeaderFactory headerFactory = protocolProvider.getHeaderFactory();

    // Event
    EventHeader evHeader;
    try {
      evHeader = headerFactory.createEventHeader(eventPackage);

      String eventId = subscription.getEventId();
      if (eventId != null) evHeader.setEventId(eventId);
    } catch (ParseException e) {
      // these two should never happen.
      logger.error("An unexpected error occurred while" + "constructing the EventHeader", e);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the EventHeader",
          OperationFailedException.INTERNAL_ERROR,
          e);
    }
    req.setHeader(evHeader);

    // Accept
    AcceptHeader accept;
    try {
      accept = headerFactory.createAcceptHeader("application", contentSubType);
    } catch (ParseException e) {
      logger.error("wrong accept header", e);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the AcceptHeader",
          OperationFailedException.INTERNAL_ERROR,
          e);
    }
    req.setHeader(accept);

    // Expires
    ExpiresHeader expHeader;
    try {
      expHeader = headerFactory.createExpiresHeader(expires);
    } catch (InvalidArgumentException e) {
      logger.error("Invalid expires value: " + expires, e);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the ExpiresHeader",
          OperationFailedException.INTERNAL_ERROR,
          e);
    }
    req.setHeader(expHeader);
  }
Exemple #3
0
 /**
  * Logs the specified message and details.
  *
  * @param message the message to log
  * @param from the message sender
  * @param to the message addressee
  * @param status message status
  * @param sender determines whether we are the origin of this message.
  * @param time the date this message was received at.
  */
 public void logMessage(
     SIPMessage message, String from, String to, String status, boolean sender, long time) {
   try {
     logPacket(message, sender);
   } catch (Throwable e) {
     logger.error("Error logging packet", e);
   }
 }
  /**
   * Sends the <tt>message</tt> to the destination indicated by the <tt>to</tt> contact.
   *
   * @param to the <tt>Contact</tt> to send <tt>message</tt> to
   * @param message the <tt>Message</tt> to send.
   * @throws java.lang.IllegalStateException if the underlying stack is not registered and
   *     initialized.
   * @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an instance of ContactImpl.
   */
  public void sendInstantMessage(Contact to, Message message)
      throws IllegalStateException, IllegalArgumentException {
    if (!(to instanceof ContactSipImpl))
      throw new IllegalArgumentException("The specified contact is not a Sip contact." + to);

    assertConnected();

    // offline message
    if (to.getPresenceStatus().equals(sipStatusEnum.getStatus(SipStatusEnum.OFFLINE))
        && !offlineMessageSupported) {
      if (logger.isDebugEnabled()) logger.debug("trying to send a message to an offline contact");
      fireMessageDeliveryFailed(
          message, to, MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED);
      return;
    }

    // create the message
    Request mes;
    try {
      mes = createMessageRequest(to, message);
    } catch (OperationFailedException ex) {
      logger.error("Failed to create the message.", ex);

      fireMessageDeliveryFailed(message, to, MessageDeliveryFailedEvent.INTERNAL_ERROR);
      return;
    }

    try {
      sendMessageRequest(mes, to, message);
    } catch (TransactionUnavailableException ex) {
      logger.error(
          "Failed to create messageTransaction.\n"
              + "This is most probably a network connection error.",
          ex);

      fireMessageDeliveryFailed(message, to, MessageDeliveryFailedEvent.NETWORK_FAILURE);
      return;
    } catch (SipException ex) {
      logger.error("Failed to send the message.", ex);

      fireMessageDeliveryFailed(message, to, MessageDeliveryFailedEvent.INTERNAL_ERROR);
      return;
    }
  }
  /**
   * Put the stack in a state where it can receive data on three UDP/TCP ports (2 for clear
   * communication, 1 for TLS). That is to say create the related JAIN-SIP <tt>ListeningPoint</tt>s
   * and <tt>SipProvider</tt>s.
   *
   * @throws OperationFailedException if creating one of the underlying <tt>SipProvider</tt>s fails
   *     for whatever reason.
   */
  private void startListening() throws OperationFailedException {
    try {
      int bindRetriesValue = getBindRetriesValue();

      this.createProvider(this.getPreferredClearPort(), bindRetriesValue, false);
      this.createProvider(this.getPreferredSecurePort(), bindRetriesValue, true);
      this.stack.start();
      if (logger.isTraceEnabled()) logger.trace("started listening");
    } catch (Exception ex) {
      logger.error(
          "An unexpected error happened while creating the" + "SipProviders and ListeningPoints.");
      throw new OperationFailedException(
          "An unexpected error hapenned" + "while initializing the SIP stack",
          OperationFailedException.INTERNAL_ERROR,
          ex);
    }
  }
  /**
   * Dispatches the event received from a JAIN-SIP <tt>SipProvider</tt> to one of our "candidate
   * recipient" listeners.
   *
   * @param event the event received for a <tt>SipProvider</tt>.
   */
  public void processResponse(ResponseEvent event) {
    try {
      // we don't have to accept the transaction since we
      // created the request
      ClientTransaction transaction = event.getClientTransaction();
      if (logger.isTraceEnabled())
        logger.trace(
            "received response: "
                + event.getResponse().getStatusCode()
                + " "
                + event.getResponse().getReasonPhrase());

      if (transaction == null) {
        logger.warn("Transaction is null, probably already expired!");
        return;
      }

      ProtocolProviderServiceSipImpl service = getServiceData(transaction);
      if (service != null) {
        // Mark the dialog for the dispatching of later in-dialog
        // responses. If there is no dialog then the initial request
        // sure is marked otherwise we won't have found the service with
        // getServiceData(). The request has to be marked in case we
        // receive one more response in an out-of-dialog transaction.
        if (event.getDialog() != null) {
          SipApplicationData.setApplicationData(
              event.getDialog(), SipApplicationData.KEY_SERVICE, service);
        }
        service.processResponse(event);
      } else {
        logger.error(
            "We received a response which "
                + "wasn't marked, please report this to "
                + "*****@*****.**");
      }
    } catch (Throwable exc) {
      // any exception thrown within our code should be caught here
      // so that we could log it rather than interrupt stack activity with
      // it.
      this.logApplicationException(DialogTerminatedEvent.class, exc);
    }
  }
 /**
  * Dispatches the event received from a JAIN-SIP <tt>SipProvider</tt> to one of our "candidate
  * recipient" listeners.
  *
  * @param event the event received for a <tt>SipProvider</tt>.
  */
 public void processDialogTerminated(DialogTerminatedEvent event) {
   try {
     ProtocolProviderServiceSipImpl recipient =
         (ProtocolProviderServiceSipImpl)
             SipApplicationData.getApplicationData(
                 event.getDialog(), SipApplicationData.KEY_SERVICE);
     if (recipient == null) {
       logger.error(
           "Dialog wasn't marked, please report this to " + "*****@*****.**");
     } else {
       if (logger.isTraceEnabled()) logger.trace("service was found with dialog data");
       recipient.processDialogTerminated(event);
     }
   } catch (Throwable exc) {
     // any exception thrown within our code should be caught here
     // so that we could log it rather than interrupt stack activity with
     // it.
     this.logApplicationException(DialogTerminatedEvent.class, exc);
   }
 }
  /**
   * Creates a new SUBSCRIBE request in the form of a <tt>ClientTransaction</tt> with the parameters
   * of a specific <tt>Subscription</tt>.
   *
   * @param subscription the <tt>Subscription</tt> to be described in a SUBSCRIBE request
   * @param dialog the <tt>Dialog</tt> with which this request should be associated
   * @param expires the subscription duration of the SUBSCRIBE request to be created
   * @return a new <tt>ClientTransaction</tt> initialized with a new SUBSCRIBE request which matches
   *     the parameters of the specified <tt>Subscription</tt> and is associated with the specified
   *     <tt>Dialog</tt>
   * @throws OperationFailedException if the message could not be generated
   */
  private ClientTransaction createSubscription(
      Subscription subscription, Dialog dialog, int expires) throws OperationFailedException {
    Request req = messageFactory.createRequest(dialog, Request.SUBSCRIBE);

    // Address
    Address toAddress = dialog.getRemoteTarget();
    // no Contact field
    if (toAddress == null) toAddress = dialog.getRemoteParty();

    // MaxForwards
    MaxForwardsHeader maxForwards = protocolProvider.getMaxForwardsHeader();
    req.setHeader(maxForwards);

    /*
     * Create the transaction and then add the via header as recommended by
     * the jain-sip documentation at
     * http://snad.ncsl.nist.gov/proj/iptel/jain-sip-1.2/javadoc
     * /javax/sip/Dialog.html#createRequest(String).
     */
    ClientTransaction transac = null;
    try {
      transac = protocolProvider.getDefaultJainSipProvider().getNewClientTransaction(req);
    } catch (TransactionUnavailableException ex) {
      logger.error(
          "Failed to create subscriptionTransaction.\n"
              + "This is most probably a network connection error.",
          ex);
      throw new OperationFailedException(
          "Failed to create the subscription transaction",
          OperationFailedException.NETWORK_FAILURE);
    }

    populateSubscribeRequest(req, subscription, expires);

    return transac;
  }
  /**
   * Dispatches the event received from a JAIN-SIP <tt>SipProvider</tt> to one of our "candidate
   * recipient" listeners.
   *
   * @param event the event received for a <tt>SipProvider</tt>.
   */
  public void processRequest(RequestEvent event) {
    try {
      Request request = event.getRequest();
      if (logger.isTraceEnabled()) logger.trace("received request: " + request.getMethod());

      /*
       * Create the transaction if it doesn't exist yet. If it is a
       * dialog-creating request, the dialog will also be automatically
       * created by the stack.
       */
      if (event.getServerTransaction() == null) {
        try {
          // apply some hacks if needed on incoming request
          // to be compliant with some servers/clients
          // if needed stop further processing.
          if (applyNonConformanceHacks(event)) return;

          SipProvider source = (SipProvider) event.getSource();
          ServerTransaction transaction = source.getNewServerTransaction(request);

          /*
           * Update the event, otherwise getServerTransaction() and
           * getDialog() will still return their previous value.
           */
          event = new RequestEvent(source, transaction, transaction.getDialog(), request);
        } catch (SipException ex) {
          logger.error(
              "couldn't create transaction, please report "
                  + "this to [email protected]",
              ex);
        }
      }

      ProtocolProviderServiceSipImpl service = getServiceData(event.getServerTransaction());
      if (service != null) {
        service.processRequest(event);
      } else {
        service = findTargetFor(request);
        if (service == null) {
          logger.error("couldn't find a ProtocolProviderServiceSipImpl " + "to dispatch to");
          if (event.getServerTransaction() != null) event.getServerTransaction().terminate();
        } else {

          /*
           * Mark the dialog for the dispatching of later in-dialog
           * requests. If there is no dialog, we need to mark the
           * request to dispatch a possible timeout when sending the
           * response.
           */
          Object container = event.getDialog();
          if (container == null) container = request;
          SipApplicationData.setApplicationData(container, SipApplicationData.KEY_SERVICE, service);

          service.processRequest(event);
        }
      }
    } catch (Throwable exc) {

      /*
       * Any exception thrown within our code should be caught here so
       * that we could log it rather than interrupt stack activity with
       * it.
       */
      this.logApplicationException(DialogTerminatedEvent.class, exc);

      // Unfortunately, death can hardly be ignored.
      if (exc instanceof ThreadDeath) throw (ThreadDeath) exc;
    }
  }
  /**
   * Attach JAIN-SIP <tt>SipProvider</tt> and <tt>ListeningPoint</tt> to the stack either for clear
   * communications or TLS. Clear UDP and TCP <tt>ListeningPoint</tt>s are not handled separately as
   * the former is a fallback for the latter (depending on the size of the data transmitted). Both
   * <tt>ListeningPoint</tt>s must be bound to the same address and port in order for the related
   * <tt>SipProvider</tt> to be created. If a UDP or TCP <tt>ListeningPoint</tt> cannot bind, retry
   * for both on another port.
   *
   * @param preferredPort which port to try first to bind.
   * @param retries how many times should we try to find a free port to bind
   * @param secure whether to create the TLS SipProvider. or the clear UDP/TCP one.
   * @throws TransportNotSupportedException in case we try to create a provider for a transport not
   *     currently supported by jain-sip
   * @throws InvalidArgumentException if we try binding to an illegal port (which we won't)
   * @throws ObjectInUseException if another <tt>SipProvider</tt> is already associated with this
   *     <tt>ListeningPoint</tt>.
   * @throws TransportAlreadySupportedException if there is already a ListeningPoint associated to
   *     this <tt>SipProvider</tt> with the same transport of the <tt>ListeningPoint</tt>.
   * @throws TooManyListenersException if we try to add a new <tt>SipListener</tt> with a
   *     <tt>SipProvider</tt> when one was already registered.
   */
  private void createProvider(int preferredPort, int retries, boolean secure)
      throws TransportNotSupportedException, InvalidArgumentException, ObjectInUseException,
          TransportAlreadySupportedException, TooManyListenersException {
    String context = (secure ? "TLS: " : "clear UDP/TCP: ");

    if (retries < 0) {
      // very unlikely to happen with the default 50 retries
      logger.error(context + "couldn't find free ports to listen on.");
      return;
    }

    ListeningPoint tlsLP = null;
    ListeningPoint udpLP = null;
    ListeningPoint tcpLP = null;

    try {
      if (secure) {
        tlsLP =
            this.stack.createListeningPoint(
                NetworkUtils.IN_ADDR_ANY, preferredPort, ListeningPoint.TLS);
        if (logger.isTraceEnabled()) logger.trace("TLS secure ListeningPoint has been created.");

        this.secureJainSipProvider = this.stack.createSipProvider(tlsLP);
        this.secureJainSipProvider.addSipListener(this);
      } else {
        udpLP =
            this.stack.createListeningPoint(
                NetworkUtils.IN_ADDR_ANY, preferredPort, ListeningPoint.UDP);
        tcpLP =
            this.stack.createListeningPoint(
                NetworkUtils.IN_ADDR_ANY, preferredPort, ListeningPoint.TCP);
        if (logger.isTraceEnabled())
          logger.trace("UDP and TCP clear ListeningPoints have " + "been created.");

        this.clearJainSipProvider = this.stack.createSipProvider(udpLP);
        this.clearJainSipProvider.addListeningPoint(tcpLP);
        this.clearJainSipProvider.addSipListener(this);
      }

      if (logger.isTraceEnabled()) logger.trace(context + "SipProvider has been created.");
    } catch (InvalidArgumentException ex) {
      // makes sure we didn't leave an open listener
      // as both UDP and TCP listener have to bind to the same port
      if (tlsLP != null) this.stack.deleteListeningPoint(tlsLP);
      if (udpLP != null) this.stack.deleteListeningPoint(udpLP);
      if (tcpLP != null) this.stack.deleteListeningPoint(tcpLP);

      // FIXME: "Address already in use" is not working
      // as ex.getMessage() displays in the locale language in SC
      // (getMessage() is always supposed to be English though)
      // this should be a temporary workaround
      // if (ex.getMessage().indexOf("Address already in use") != -1)
      // another software is probably using the port
      if (ex.getCause() instanceof java.io.IOException) {
        if (logger.isDebugEnabled())
          logger.debug("Port " + preferredPort + " seems in use for either TCP or UDP.");

        // tries again on a new random port
        int currentlyTriedPort = NetworkUtils.getRandomPortNumber();
        if (logger.isDebugEnabled()) logger.debug("Retrying bind on port " + currentlyTriedPort);
        this.createProvider(currentlyTriedPort, retries - 1, secure);
      } else throw ex;
    }
  }
Exemple #11
0
  /**
   * Logs the specified message and details to the packet logging service if enabled.
   *
   * @param message the message to log
   * @param sender determines whether we are the origin of this message.
   */
  private void logPacket(SIPMessage message, boolean sender) {
    try {
      PacketLoggingService packetLogging = SipActivator.getPacketLogging();
      if (packetLogging == null
          || !packetLogging.isLoggingEnabled(PacketLoggingService.ProtocolName.SIP)
          /* Via not present in CRLF packet on TCP - causes NPE */
          || message.getTopmostVia() == null) return;

      String transport = message.getTopmostVia().getTransport();
      boolean isTransportUDP = transport.equalsIgnoreCase("UDP");

      byte[] srcAddr;
      int srcPort;
      byte[] dstAddr;
      int dstPort;

      // if addresses are not set use empty byte array with length
      // equals to the other address or just empty
      // byte array with length 4 (ipv4 0.0.0.0)
      if (sender) {
        if (!isTransportUDP) {
          InetSocketAddress localAddress =
              getLocalAddressForDestination(
                  message.getRemoteAddress(),
                  message.getRemotePort(),
                  message.getLocalAddress(),
                  transport);
          srcPort = localAddress.getPort();
          srcAddr = localAddress.getAddress().getAddress();
        } else {
          srcPort = message.getLocalPort();
          if (message.getLocalAddress() != null) srcAddr = message.getLocalAddress().getAddress();
          else if (message.getRemoteAddress() != null)
            srcAddr = new byte[message.getRemoteAddress().getAddress().length];
          else srcAddr = new byte[4];
        }

        dstPort = message.getRemotePort();
        if (message.getRemoteAddress() != null) dstAddr = message.getRemoteAddress().getAddress();
        else dstAddr = new byte[srcAddr.length];
      } else {
        if (!isTransportUDP) {
          InetSocketAddress dstAddress =
              getLocalAddressForDestination(
                  message.getRemoteAddress(),
                  message.getRemotePort(),
                  message.getLocalAddress(),
                  transport);
          dstPort = dstAddress.getPort();
          dstAddr = dstAddress.getAddress().getAddress();
        } else {
          dstPort = message.getLocalPort();
          if (message.getLocalAddress() != null) dstAddr = message.getLocalAddress().getAddress();
          else if (message.getRemoteAddress() != null)
            dstAddr = new byte[message.getRemoteAddress().getAddress().length];
          else dstAddr = new byte[4];
        }

        srcPort = message.getRemotePort();
        if (message.getRemoteAddress() != null) srcAddr = message.getRemoteAddress().getAddress();
        else srcAddr = new byte[dstAddr.length];
      }

      byte[] msg = null;
      if (message instanceof SIPRequest) {
        SIPRequest req = (SIPRequest) message;
        if (req.getMethod().equals(SIPRequest.MESSAGE)
            && message.getContentTypeHeader() != null
            && message.getContentTypeHeader().getContentType().equalsIgnoreCase("text")) {
          int len = req.getContentLength().getContentLength();

          if (len > 0) {
            SIPRequest newReq = (SIPRequest) req.clone();

            byte[] newContent = new byte[len];
            Arrays.fill(newContent, (byte) '.');
            newReq.setMessageContent(newContent);
            msg = newReq.toString().getBytes("UTF-8");
          }
        }
      }

      if (msg == null) {
        msg = message.toString().getBytes("UTF-8");
      }

      packetLogging.logPacket(
          PacketLoggingService.ProtocolName.SIP,
          srcAddr,
          srcPort,
          dstAddr,
          dstPort,
          isTransportUDP
              ? PacketLoggingService.TransportName.UDP
              : PacketLoggingService.TransportName.TCP,
          sender,
          msg);
    } catch (Throwable e) {
      logger.error("Cannot obtain message body", e);
    }
  }
Exemple #12
0
 /**
  * Logs an exception and an error message error message.
  *
  * @param message that message that we'd like to log.
  * @param ex the exception that we'd like to log.
  */
 public void logError(String message, Exception ex) {
   logger.error("Error from the JAIN-SIP stack: " + message, ex);
 }
  /**
   * Implements {@link MethodProcessor#processResponse(ResponseEvent)}. Handles only responses to
   * SUBSCRIBE requests because they are the only requests concerning event package subscribers (and
   * the only requests sent by them, for that matter) and if the processing of a given response
   * requires event package-specific handling, delivers the response to the matching
   * <tt>Subscription</tt> instance. Examples of such event package-specific handling include
   * letting the respective <tt>Subscription</tt> handle the success or failure in the establishment
   * of a subscription.
   *
   * @param responseEvent a <tt>ResponseEvent</tt> specifying the SIP <tt>Response</tt> to be
   *     processed
   * @return <tt>true</tt> if the SIP <tt>Response</tt> specified by <tt>responseEvent</tt> was
   *     processed; otherwise, <tt>false</tt>
   */
  @Override
  public boolean processResponse(ResponseEvent responseEvent) {
    Response response = responseEvent.getResponse();

    CSeqHeader cseqHeader = (CSeqHeader) response.getHeader(CSeqHeader.NAME);
    if (cseqHeader == null) {
      logger.error("An incoming response did not contain a CSeq header");
      return false;
    }
    if (!Request.SUBSCRIBE.equals(cseqHeader.getMethod())) return false;

    ClientTransaction clientTransaction = responseEvent.getClientTransaction();
    Request request = clientTransaction.getRequest();

    /*
     * Don't handle responses to requests not coming from this event
     * package.
     */
    if (request != null) {
      EventHeader eventHeader = (EventHeader) request.getHeader(EventHeader.NAME);
      if ((eventHeader == null) || !eventPackage.equalsIgnoreCase(eventHeader.getEventType()))
        return false;
    }

    // Find the subscription.
    CallIdHeader callIdHeader = (CallIdHeader) response.getHeader(CallIdHeader.NAME);
    String callId = callIdHeader.getCallId();
    Subscription subscription = getSubscription(callId);

    // if it's the response to an unsubscribe message, we just ignore it
    // whatever the response is however if we need to handle a
    // challenge, we do it
    ExpiresHeader expHeader = response.getExpires();
    int statusCode = response.getStatusCode();
    SipProvider sourceProvider = (SipProvider) responseEvent.getSource();
    if (((expHeader != null) && (expHeader.getExpires() == 0))
        || (subscription == null)) // this handle the unsubscription
    // case where we removed the contact
    // from subscribedContacts
    {
      boolean processed = false;

      if ((statusCode == Response.UNAUTHORIZED)
          || (statusCode == Response.PROXY_AUTHENTICATION_REQUIRED)) {
        try {
          processAuthenticationChallenge(clientTransaction, response, sourceProvider);
          processed = true;
        } catch (OperationFailedException e) {
          logger.error("can't handle the challenge", e);
        }
      } else if ((statusCode != Response.OK) && (statusCode != Response.ACCEPTED)) processed = true;

      // any other cases (200/202) will imply a NOTIFY, so we will
      // handle the end of a subscription there
      return processed;
    }

    if ((statusCode >= Response.OK) && (statusCode < Response.MULTIPLE_CHOICES)) {
      // OK (200/202)
      if ((statusCode == Response.OK) || (statusCode == Response.ACCEPTED)) {
        if (expHeader == null) {
          // not conform to rfc3265
          logger.error("no Expires header in this response");
          return false;
        }

        SubscriptionRefreshTask refreshTask = new SubscriptionRefreshTask(subscription);
        subscription.setTimerTask(refreshTask);

        int refreshDelay = expHeader.getExpires();
        // try to keep a margin if the refresh delay allows it
        if (refreshDelay >= (2 * refreshMargin)) refreshDelay -= refreshMargin;
        timer.schedule(refreshTask, refreshDelay * 1000);

        // do it to remember the dialog in case of a polling
        // subscription (which means no call to finalizeSubscription)
        subscription.setDialog(clientTransaction.getDialog());

        subscription.processSuccessResponse(responseEvent, statusCode);
      }
    } else if ((statusCode >= Response.MULTIPLE_CHOICES) && (statusCode < Response.BAD_REQUEST)) {
      if (logger.isInfoEnabled())
        logger.info(
            "Response to subscribe to "
                + subscription.getAddress()
                + ": "
                + response.getReasonPhrase());
    } else if (statusCode >= Response.BAD_REQUEST) {
      // if the response is a 423 response, just re-send the request
      // with a valid expires value
      if (statusCode == Response.INTERVAL_TOO_BRIEF) {
        MinExpiresHeader min = (MinExpiresHeader) response.getHeader(MinExpiresHeader.NAME);

        if (min == null) {
          logger.error("no minimal expires value in this 423 " + "response");
          return false;
        }

        ExpiresHeader exp = request.getExpires();

        try {
          exp.setExpires(min.getExpires());
        } catch (InvalidArgumentException e) {
          logger.error("can't set the new expires value", e);
          return false;
        }

        ClientTransaction transac = null;
        try {
          transac = protocolProvider.getDefaultJainSipProvider().getNewClientTransaction(request);
        } catch (TransactionUnavailableException e) {
          logger.error("can't create the client transaction", e);
          return false;
        }

        try {
          transac.sendRequest();
        } catch (SipException e) {
          logger.error("can't send the new request", e);
          return false;
        }

        return true;
        // UNAUTHORIZED (401/407)
      } else if ((statusCode == Response.UNAUTHORIZED)
          || (statusCode == Response.PROXY_AUTHENTICATION_REQUIRED)) {
        try {
          processAuthenticationChallenge(clientTransaction, response, sourceProvider);
        } catch (OperationFailedException e) {
          logger.error("can't handle the challenge", e);

          removeSubscription(callId, subscription);
          subscription.processFailureResponse(responseEvent, statusCode);
        }
        // 408 480 486 600 603 : non definitive reject
        // others: definitive reject (or not implemented)
      } else {
        if (logger.isDebugEnabled()) logger.debug("error received from the network:\n" + response);

        removeSubscription(callId, subscription);
        subscription.processFailureResponse(responseEvent, statusCode);
      }
    }

    return true;
  }
  /**
   * Implements {@link MethodProcessor#processRequest(RequestEvent)}. Handles only NOTIFY requests
   * because they are the only requests concerning event package subscribers and if the processing
   * of a given request requires event package-specific handling, delivers the request to the
   * matching Subscription instance. Examples of such event package-specific handling include
   * handling the termination of an existing Subscription and processing the bodies of the NOTIFY
   * requests for active Subscriptions.
   *
   * @param requestEvent a <tt>RequestEvent</tt> specifying the SIP <tt>Request</tt> to be processed
   * @return <tt>true</tt> if the SIP <tt>Request</tt> specified by <tt>requestEvent</tt> was
   *     processed; otherwise, <tt>false</tt>
   */
  @Override
  public boolean processRequest(RequestEvent requestEvent) {
    Request request = requestEvent.getRequest();

    EventHeader eventHeader = (EventHeader) request.getHeader(EventHeader.NAME);
    if ((eventHeader == null) || !eventPackage.equalsIgnoreCase(eventHeader.getEventType())) {
      /*
       * We are not concerned by this request, perhaps another listener
       * is. So don't send a 489 / Bad event answer here.
       */
      return false;
    }

    if (!Request.NOTIFY.equals(request.getMethod())) return false;

    if (logger.isDebugEnabled()) logger.debug("notify received");

    SubscriptionStateHeader sstateHeader =
        (SubscriptionStateHeader) request.getHeader(SubscriptionStateHeader.NAME);
    // notify must contain one (rfc3265)
    if (sstateHeader == null) {
      logger.error("no subscription state in this request");
      return false;
    }
    String sstate = sstateHeader.getState();

    ServerTransaction serverTransaction = getOrCreateServerTransaction(requestEvent);

    // first handle the case of a contact still pending
    // it's possible if the NOTIFY arrives before the OK
    CallIdHeader callIdHeader = (CallIdHeader) request.getHeader(CallIdHeader.NAME);
    String callId = callIdHeader.getCallId();
    Subscription subscription = getSubscription(callId);

    // see if the notify correspond to an existing subscription
    if ((subscription == null) && !SubscriptionStateHeader.TERMINATED.equalsIgnoreCase(sstate)) {
      if (logger.isDebugEnabled()) logger.debug("subscription not found for callId " + callId);

      // send a 481 response (rfc3625)
      Response response;
      try {
        response =
            protocolProvider
                .getMessageFactory()
                .createResponse(Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST, request);
      } catch (ParseException e) {
        logger.error("failed to create the 481 response", e);
        return false;
      }

      try {
        serverTransaction.sendResponse(response);
      } catch (SipException e) {
        logger.error("failed to send the response", e);
      } catch (InvalidArgumentException e) {
        // should not happen
        logger.error("invalid argument provided while trying to send the response", e);
      }
      return true;
    }

    // if we don't understand the content
    ContentTypeHeader ctheader = (ContentTypeHeader) request.getHeader(ContentTypeHeader.NAME);
    if ((ctheader != null) && !ctheader.getContentSubType().equalsIgnoreCase(contentSubType)) {
      // send a 415 response (rfc3261)
      Response response;
      try {
        response =
            protocolProvider
                .getMessageFactory()
                .createResponse(Response.UNSUPPORTED_MEDIA_TYPE, request);
      } catch (ParseException e) {
        logger.error("failed to create the OK response", e);
        return false;
      }

      // we want PIDF
      AcceptHeader acceptHeader;
      try {
        acceptHeader =
            protocolProvider.getHeaderFactory().createAcceptHeader("application", contentSubType);
      } catch (ParseException e) {
        // should not happen
        logger.error("failed to create the accept header", e);
        return false;
      }
      response.setHeader(acceptHeader);

      try {
        serverTransaction.sendResponse(response);
      } catch (SipException e) {
        logger.error("failed to send the response", e);
      } catch (InvalidArgumentException e) {
        // should not happen
        logger.error("invalid argument provided while trying" + " to send the response", e);
      }
    }

    // if the presentity doesn't want of us anymore
    if (SubscriptionStateHeader.TERMINATED.equalsIgnoreCase(sstate)) {
      // if we requested this end of subscription, subscription == null
      if (subscription != null) {
        removeSubscription(callId, subscription);
        subscription.processTerminatedRequest(requestEvent, sstateHeader.getReasonCode());
      }
    }

    // send an OK response
    Response response;
    try {
      response = protocolProvider.getMessageFactory().createResponse(Response.OK, request);
    } catch (ParseException e) {
      logger.error("failed to create the OK response", e);
      return false;
    }

    try {
      serverTransaction.sendResponse(response);
    } catch (SipException e) {
      logger.error("failed to send the response", e);
    } catch (InvalidArgumentException e) {
      // should not happen
      logger.error("invalid argument provided while trying to send the response", e);
    }

    // transform the presence document in new presence status
    if (subscription != null)
      subscription.processActiveRequest(requestEvent, request.getRawContent());

    return true;
  }
  /**
   * Find the <tt>ProtocolProviderServiceSipImpl</tt> (one of our "candidate recipient" listeners)
   * which this <tt>request</tt> should be dispatched to. The strategy is to look first at the
   * request URI, and then at the To field to find a matching candidate for dispatching. Note that
   * this method takes a <tt>Request</tt> as param, and not a <tt>ServerTransaction</tt>, because
   * sometimes <tt>RequestEvent</tt>s have no associated <tt>ServerTransaction</tt>.
   *
   * @param request the <tt>Request</tt> to find a recipient for.
   * @return a suitable <tt>ProtocolProviderServiceSipImpl</tt>.
   */
  private ProtocolProviderServiceSipImpl findTargetFor(Request request) {
    if (request == null) {
      logger.error("request shouldn't be null.");
      return null;
    }

    List<ProtocolProviderServiceSipImpl> currentListenersCopy =
        new ArrayList<ProtocolProviderServiceSipImpl>(this.getSipListeners());

    // Let's first narrow down candidate choice by comparing
    // addresses and ports (no point in delivering to a provider with a
    // non matching IP address  since they will reject it anyway).
    filterByAddress(currentListenersCopy, request);

    if (currentListenersCopy.size() == 0) {
      logger.error("no listeners");
      return null;
    }

    URI requestURI = request.getRequestURI();

    if (requestURI.isSipURI()) {
      String requestUser = ((SipURI) requestURI).getUser();

      List<ProtocolProviderServiceSipImpl> candidates =
          new ArrayList<ProtocolProviderServiceSipImpl>();

      // check if the Request-URI username is
      // one of ours usernames
      for (ProtocolProviderServiceSipImpl listener : currentListenersCopy) {
        String ourUserID = listener.getAccountID().getUserID();
        // logger.trace(ourUserID + " *** " + requestUser);
        if (ourUserID.equals(requestUser)) {
          if (logger.isTraceEnabled())
            logger.trace("suitable candidate found: " + listener.getAccountID());
          candidates.add(listener);
        }
      }

      // the perfect match
      // every other case is approximation
      if (candidates.size() == 1) {
        ProtocolProviderServiceSipImpl perfectMatch = candidates.get(0);

        if (logger.isTraceEnabled())
          logger.trace("Will dispatch to \"" + perfectMatch.getAccountID() + "\"");
        return perfectMatch;
      }

      // more than one account match
      if (candidates.size() > 1) {
        // check if a custom param exists in the contact
        // address (set for registrar accounts)
        for (ProtocolProviderServiceSipImpl candidate : candidates) {
          String hostValue =
              ((SipURI) requestURI).getParameter(SipStackSharing.CONTACT_ADDRESS_CUSTOM_PARAM_NAME);
          if (hostValue == null) continue;
          if (hostValue.equals(candidate.getContactAddressCustomParamValue())) {
            if (logger.isTraceEnabled())
              logger.trace(
                  "Will dispatch to \""
                      + candidate.getAccountID()
                      + "\" because "
                      + "\" the custom param was set");
            return candidate;
          }
        }

        // Past this point, our guess is not reliable. We try to find
        // the "least worst" match based on parameters like the To field

        // check if the To header field host part
        // matches any of our SIP hosts
        for (ProtocolProviderServiceSipImpl candidate : candidates) {
          URI fromURI = ((FromHeader) request.getHeader(FromHeader.NAME)).getAddress().getURI();
          if (fromURI.isSipURI() == false) continue;
          SipURI ourURI = (SipURI) candidate.getOurSipAddress((SipURI) fromURI).getURI();
          String ourHost = ourURI.getHost();

          URI toURI = ((ToHeader) request.getHeader(ToHeader.NAME)).getAddress().getURI();
          if (toURI.isSipURI() == false) continue;
          String toHost = ((SipURI) toURI).getHost();

          // logger.trace(toHost + "***" + ourHost);
          if (toHost.equals(ourHost)) {
            if (logger.isTraceEnabled())
              logger.trace(
                  "Will dispatch to \""
                      + candidate.getAccountID()
                      + "\" because "
                      + "host in the To: is the same as in our AOR");
            return candidate;
          }
        }

        // fallback on the first candidate
        ProtocolProviderServiceSipImpl target = candidates.iterator().next();
        logger.info(
            "Will randomly dispatch to \""
                + target.getAccountID()
                + "\" because there is ambiguity on the username from"
                + " the Request-URI");
        if (logger.isTraceEnabled()) logger.trace("\n" + request);
        return target;
      }

      // fallback on any account
      ProtocolProviderServiceSipImpl target = currentListenersCopy.iterator().next();
      if (logger.isDebugEnabled())
        logger.debug(
            "Will randomly dispatch to \""
                + target.getAccountID()
                + "\" because the username in the Request-URI "
                + "is unknown or empty");
      if (logger.isTraceEnabled()) logger.trace("\n" + request);
      return target;
    } else {
      logger.error("Request-URI is not a SIP URI, dropping");
    }
    return null;
  }
  /**
   * Logs exceptions that have occurred in the application while processing events originating from
   * the stack.
   *
   * @param eventClass the class of the jain-sip event that we were handling when the exception was
   *     thrown.
   * @param exc the exception that we need to log.
   */
  private void logApplicationException(Class<DialogTerminatedEvent> eventClass, Throwable exc) {
    String message = "An error occurred while processing event of type: " + eventClass.getName();

    logger.error(message, exc);
    if (logger.isDebugEnabled()) logger.debug(message, exc);
  }
Exemple #17
0
 /**
  * Log an error message.
  *
  * @param message error message to log.
  */
 public void logError(String message) {
   logger.error("Error from the JAIN-SIP stack: " + message);
 }
  /**
   * Construct a <tt>Request</tt> represent a new message.
   *
   * @param to the <tt>Contact</tt> to send <tt>message</tt> to
   * @param message the <tt>Message</tt> to send.
   * @return a Message Request destined to the contact
   * @throws OperationFailedException if an error occurred during the creation of the request
   */
  Request createMessageRequest(Contact to, Message message) throws OperationFailedException {
    Address toAddress = null;
    try {
      toAddress = sipProvider.parseAddressString(to.getAddress());
    } catch (ParseException exc) {
      // Shouldn't happen
      logger.error("An unexpected error occurred while" + "constructing the address", exc);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the address",
          OperationFailedException.INTERNAL_ERROR,
          exc);
    }

    // Call ID
    CallIdHeader callIdHeader = this.sipProvider.getDefaultJainSipProvider().getNewCallId();

    // CSeq
    CSeqHeader cSeqHeader = null;

    try {
      // protect seqN
      synchronized (this) {
        cSeqHeader = this.sipProvider.getHeaderFactory().createCSeqHeader(seqN++, Request.MESSAGE);
      }
    } catch (InvalidArgumentException ex) {
      // Shouldn't happen
      logger.error("An unexpected error occurred while" + "constructing the CSeqHeadder", ex);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the CSeqHeadder",
          OperationFailedException.INTERNAL_ERROR,
          ex);
    } catch (ParseException exc) {
      // shouldn't happen
      logger.error("An unexpected error occurred while" + "constructing the CSeqHeadder", exc);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the CSeqHeadder",
          OperationFailedException.INTERNAL_ERROR,
          exc);
    }

    // FromHeader and ToHeader
    String localTag = SipMessageFactory.generateLocalTag();
    FromHeader fromHeader = null;
    ToHeader toHeader = null;
    try {
      // FromHeader
      fromHeader =
          this.sipProvider
              .getHeaderFactory()
              .createFromHeader(sipProvider.getOurSipAddress(toAddress), localTag);

      // ToHeader
      toHeader = this.sipProvider.getHeaderFactory().createToHeader(toAddress, null);
    } catch (ParseException ex) {
      // these two should never happen.
      logger.error(
          "An unexpected error occurred while" + "constructing the FromHeader or ToHeader", ex);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the FromHeader or ToHeader",
          OperationFailedException.INTERNAL_ERROR,
          ex);
    }

    // ViaHeaders
    ArrayList<ViaHeader> viaHeaders = this.sipProvider.getLocalViaHeaders(toAddress);

    // MaxForwards
    MaxForwardsHeader maxForwards = this.sipProvider.getMaxForwardsHeader();

    // Content params
    ContentTypeHeader contTypeHeader;
    ContentLengthHeader contLengthHeader;
    try {
      contTypeHeader =
          this.sipProvider
              .getHeaderFactory()
              .createContentTypeHeader(getType(message), getSubType(message));

      if (!DEFAULT_MIME_ENCODING.equalsIgnoreCase(message.getEncoding()))
        contTypeHeader.setParameter("charset", message.getEncoding());

      contLengthHeader =
          this.sipProvider.getHeaderFactory().createContentLengthHeader(message.getSize());
    } catch (ParseException ex) {
      // these two should never happen.
      logger.error("An unexpected error occurred while" + "constructing the content headers", ex);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the content headers",
          OperationFailedException.INTERNAL_ERROR,
          ex);
    } catch (InvalidArgumentException exc) {
      // these two should never happen.
      logger.error(
          "An unexpected error occurred while" + "constructing the content length header", exc);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the content length header",
          OperationFailedException.INTERNAL_ERROR,
          exc);
    }

    Request req;
    try {
      req =
          this.sipProvider
              .getMessageFactory()
              .createRequest(
                  toHeader.getAddress().getURI(),
                  Request.MESSAGE,
                  callIdHeader,
                  cSeqHeader,
                  fromHeader,
                  toHeader,
                  viaHeaders,
                  maxForwards,
                  contTypeHeader,
                  message.getRawData());
    } catch (ParseException ex) {
      // shouldn't happen
      logger.error("Failed to create message Request!", ex);
      throw new OperationFailedException(
          "Failed to create message Request!", OperationFailedException.INTERNAL_ERROR, ex);
    }

    req.addHeader(contLengthHeader);

    return req;
  }
  /**
   * Creates a new SUBSCRIBE request in the form of a <tt>ClientTransaction</tt> with the parameters
   * of a specific <tt>Subscription</tt>.
   *
   * @param subscription the <tt>Subscription</tt> to be described in a SUBSCRIBE request
   * @param expires the subscription duration of the SUBSCRIBE request to be created
   * @return a new <tt>ClientTransaction</tt> initialized with a new SUBSCRIBE request which matches
   *     the parameters of the specified <tt>Subscription</tt>
   * @throws OperationFailedException if the request could not be generated
   */
  private ClientTransaction createSubscription(Subscription subscription, int expires)
      throws OperationFailedException {
    Address toAddress = subscription.getAddress();
    HeaderFactory headerFactory = protocolProvider.getHeaderFactory();

    // Call ID
    CallIdHeader callIdHeader = protocolProvider.getDefaultJainSipProvider().getNewCallId();

    // CSeq
    CSeqHeader cSeqHeader;
    try {
      cSeqHeader = headerFactory.createCSeqHeader(1l, Request.SUBSCRIBE);
    } catch (InvalidArgumentException ex) {
      // Shouldn't happen
      logger.error("An unexpected error occurred while" + "constructing the CSeqHeader", ex);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the CSeqHeader",
          OperationFailedException.INTERNAL_ERROR,
          ex);
    } catch (ParseException ex) {
      // shouldn't happen
      logger.error("An unexpected error occurred while" + "constructing the CSeqHeader", ex);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the CSeqHeader",
          OperationFailedException.INTERNAL_ERROR,
          ex);
    }

    // FromHeader and ToHeader
    String localTag = SipMessageFactory.generateLocalTag();
    FromHeader fromHeader;
    ToHeader toHeader;
    try {
      // FromHeader
      fromHeader =
          headerFactory.createFromHeader(protocolProvider.getOurSipAddress(toAddress), localTag);

      // ToHeader
      toHeader = headerFactory.createToHeader(toAddress, null);
    } catch (ParseException ex) {
      // these two should never happen.
      logger.error(
          "An unexpected error occurred while" + "constructing the FromHeader or ToHeader", ex);
      throw new OperationFailedException(
          "An unexpected error occurred while" + "constructing the FromHeader or ToHeader",
          OperationFailedException.INTERNAL_ERROR,
          ex);
    }

    // ViaHeaders
    ArrayList<ViaHeader> viaHeaders = protocolProvider.getLocalViaHeaders(toAddress);

    // MaxForwards
    MaxForwardsHeader maxForwards = protocolProvider.getMaxForwardsHeader();

    Request req;
    try {
      req =
          protocolProvider
              .getMessageFactory()
              .createRequest(
                  toHeader.getAddress().getURI(),
                  Request.SUBSCRIBE,
                  callIdHeader,
                  cSeqHeader,
                  fromHeader,
                  toHeader,
                  viaHeaders,
                  maxForwards);
    } catch (ParseException ex) {
      // shouldn't happen
      logger.error("Failed to create message Request!", ex);
      throw new OperationFailedException(
          "Failed to create message Request!", OperationFailedException.INTERNAL_ERROR, ex);
    }

    populateSubscribeRequest(req, subscription, expires);

    // Transaction
    ClientTransaction subscribeTransaction;
    try {
      subscribeTransaction =
          protocolProvider.getDefaultJainSipProvider().getNewClientTransaction(req);
    } catch (TransactionUnavailableException ex) {
      logger.error(
          "Failed to create subscribe transaction.\n"
              + "This is most probably a network connection error.",
          ex);
      throw new OperationFailedException(
          "Failed to create the subscription transaction",
          OperationFailedException.NETWORK_FAILURE);
    }
    return subscribeTransaction;
  }