9 Back to Back User Agents

This chapter describes how to manage back-to-back user agents (B2BUAs) in Oracle Communications Converged Application Server.

A B2BUA is a Session Initiation Protocol (SIP) element that acts as an endpoint for two or more dialogs and forwards requests and responses between those two dialogs in some fashion.

B2BUAs are sometimes considered undesirable because of their potential to break services. That potential stems from the fact that they sit between two endpoints and, in some way, mediate the signaling between those endpoints. If the B2BUA doesn't know about an end-to-end service being used between those two endpoints, it may inadvertently break it.

B2BUAs are, however, an important tool for SIP application developers and as such are supported by the SIP Servlet application program interface (API). Oracle Converged Application Server supports a new helper class and some new methods to facilitate implementing B2BUAs as defined by Java Specification Request (JSR) 359, https://jcp.org/en/jsr/detail?id=359.

About Back to Back User Agents

There is no Internet Engineering Task Force (IETF) standard that defines how a B2BUA behaves; however, it is largely assumed to be an entity that receives a request, upstream, as a User Agent Server (UAS) and relays it as a new request based on the received request downstream as a User Agent Client (UAC). Effectively, the application relays the request downstream.

From a SIP servlet perspective, an application is considered a B2BUA if it indicates the routing directive as CONTINUE.

Example 9-1 illustrates explicit linking.

Example 9-1 B2BUA Routing

        +------------------------------------------+
        |                                          |
  req1  | req2 = factory.createRequest(appSession, |  req2
------->| "INVITE", req1.getFrom(), req1.getTo()); |--------->
        | // Copy headers and content from req1... |
        | req2.setRoutingDirective(CONTINUE, req1);|
        | req2.send();                             |
        +------------------------------------------+

Navigating Between the UAC and UAS Sides of a B2BUA

A common behavior of a B2BUA is to forward the requests and responses received on the UAS side of the B2BUA to the UAC side and back again. Since both UAS and UAC sides of the B2BUA contain their own SipSession objects, while receiving a request or response on one side, an application often needs to navigate to a SipSession on the other side. To facilitate this, applications can store session IDs of the peer session as an attribute in the SipSession as shown in Example 9-2.

Example 9-2 Linking SIP Sessions

public void linkSessions(SipSession s1, SipSession s2) {
  s1.setAttribute(LINKED_SESSION_ID, s2.getId());
  s2.setAttribute(LINKED_SESSION_ID, s1.getId());
}

The linked sessions can then be retrieved by using the stored attributes as shown in Example 9-3.

Example 9-3 Retrieving Linked SIP Sessions

private SipSession retrieveOtherSession(SipSession s) {
  String otherSessionId = (String) s.getAttribute(LINKED_SESSION_ID);
  return s.getApplicationSession().getSipSession(otherSessionId);
}

Many times, the UAC and UAS sides of the B2BUA need to access the SipServletRequest on the other side. To facilitate this, applications may store the request IDs of the SipServletRequest in each other as shown in Example 9-4.

Example 9-4 Linking Requests

@Bye
protected void handleBye(SipServletRequest bye1) throws IOException {
  SipSession otherSession = retrieveOtherSession(bye1.getSession());
  SipServletRequest bye2 = otherSession.createRequest("BYE");
  linkRequests(bye1, bye2);
  bye2.send();
}
private void linkRequests(SipServletRequest r1, SipServletRequest r2) {
  r1.setAttribute(LINKED_REQUEST_ID, r2.getId());
  r2.setAttribute(LINKED_REQUEST_ID, r1.getId());
}

When needed, the requests can then be accessed from the session, if they are active as shown in Example 9-5.

Example 9-5 Retrieving Requests

@SuccessResponse @Bye
protected void byeResponse(SipServletResponse r1) throws IOException {
  SipServletRequest request = retriveLinkedRequest(r1.getRequest());
  SipServletResponse r2 = request.createResponse(r1.getStatus(),
    r1.getReasonPhrase());
  r2.send();
}
private SipServletRequest retriveLinkedRequest(SipServletRequest r1) {
  String requestId = (String) r1.getAttribute(LINKED_REQUEST_ID);
  SipSession session = retrieveOtherSession(r1.getSession());
  return session.getActiveRequest(requestId);
}

ACK and PRACK Handling in B2BUA

To forward an ACK request, the B2BUA needs to access the servlet on the other side and the application needs to access final response. The application can retrieve the final response of the INVITE request by using the SipServletRequest.getFinalResponse() method. When an ACK request arrives, B2BUA first retrieves the INVITE method from the other side by using the SipSession.getActiveInvite(UAMode) method and then accesses the final response from SipServletRequest as shown in Example 9-6.

Example 9-6 B2BUA Relaying ACK

@Ack
protected void handleAck(SipServletRequest uasAck) throws IOException {
  SipSession uacSession = retrieveOtherSession(uasAck.getSession());
  SipServletRequest uacInvite = uacSession.getActiveInvite(UAMode.UAC);
  SipServletRequest uacAck = uacInvite.getFinalResponse().createAck();
  uacAck.send();
}

For reliable provisional responses, B2BUA may establish a relationship between an incoming reliable provisional response with the one relayed by the B2BUA. This can be done by saving the reliable sequence number (RSeq) and the request ID as attributes in the response as shown in Example 9-7.

Example 9-7 Linking Reliable Provisional Responses

@ProvisionalResponse
void provisionalResponse(SipServletResponse r1) throws Exception {
  SipServletRequest request = retriveLinkedRequest(r1.getRequest());
  SipServletResponse r2 = request.createResponse(r1.getStatus());
  if (r1.isReliableProvisional()) {
    String respId = r1.getProvisionalResponseId();
    r2.setAttribute(LINKED_RELIABLE_RESPONSE_ID, respId);
    r2.sendReliably();
  }
}

The linked provisional response may then be retrieved when the B2BUA forwards a PRACK as shown in Example 9-8.

Example 9-8 Forwarding a PRACK

@Prack
void handlePrack(SipServletRequest prack) throws Exception {
  SipServletResponse r1 = prack.getAcknowledgedResponse();
  SipSession session = retrieveOtherSession(prack.getSession());
  String respId = (String) r1.getAttribute(LINKED_RELIABLE_RESPONSE_ID);
  SipServletResponse r2 =
  session.getUnacknowledgedProvisionalResponse(respId);
  r2.createPrack().send();
}

B2BUA and Forking

When an INVITE request is forked downstream, one request may receive responses on different dialogs (derived sessions). Each response causes a transaction branch and is represented in the SipServlet API by the InviteBranch interface.

A B2BUA forwards such responses on different branches as a UAS to create a new InviteBranch using the SipServletRequest.createInviteBranch() method as shown in Example 9-9.

Example 9-9 Sending a Response on a Particular Branch

@Invite
void handleInviteResponse(SipServletResponse r1) throws IOException {
  InviteBranch branch = r1.getSession().getActiveInviteBranch();
  if (branch != null) {
    SipSession uasSession = retrieveOtherSession(r1.getSession());
    if (uasSession == null) {
      SipServletRequest req =
      retreiveLinkedRequest(r1.getRequest());
      branch = req.createInviteBranch();
    }
    SipServletResponse r2 =
    branch.createResponse(r1.getStatus(), r1.getReasonPhrase());
    r2.send();
  }
}

One SipServletRequest may result in more than one branch. Therefore, when there are circumstances where a B2BUA needs to access a particular branch, the B2BUA must use the corresponding InviteBranch object. For example, while relaying an ACK message, the B2BUA may access the final response from the InviteBranch object as shown in Example 9-10.

Example 9-10 Relaying an ACK on a Particular Branch

@Ack
protected void handleAck(SipServletRequest uasAck) throws IOException {
  SipSession uacSession = retrieveOtherSession(uasAck.getSession());
  InviteBranch branch = uacSession.getActiveInviteBranch();
  if (branch != null) {
    SipServletResponse response = branch.getFinalResponse();
    response.createAck().send();
  }
}

Although non-INVITE requests can also be forked, the absence of ACK and PRACK messages make their call flow much simpler. Similarly, non-INVITE requests do not support sending responses on more than one dialog from the UAS. Thus, the SIP Servlet API does not provide an API equivalent to the InviteBranch for non-INVITE requests.

The B2BUA Helper Class

The B2BUA helper class contains useful methods for simple B2BUA operations, and can be retrieved from the SipServletRequest by invoking the getB2buaHelper() method on it:

B2buaHelper getB2buaHelper() throws IllegalStateException;

Invoking the getB2buaHelper() method also indicates to Converged Application Server that this application wishes to be a B2BUA.

Any UA operation is permitted by the application but the application cannot act as proxy after that, so any invocation to getProxy() must then throw a IllegalStateException.

Similarly, the getB2buaHelper() method throws an IllegalStateException if the application has already retrieved a proxy handle by an earlier invocation of getProxy().

Note:

B2BUA helper class functionality can only be used for linking two legs (inbound and outbound) and does not support all cases of forking at the B2B or downstream. A B2BUA application may come across complex scenarios and call flows that can not be implemented by using the B2BUA helper class.

Creating a New B2BUA Request

The behavior described in this section minimizes the risk of breaking end-to-end services by copying all unknown headers from the incoming request to the outgoing request.

When an application receives an initial request for which it wishes to act as a B2BUA, it may invoke the createRequest() method available in the B2buaHelper. This method creates a request identical to the one provided as the first argument according to the following rules:

  • All unknown headers and Route, From, and To headers are copied from the original request to the new request. The container assigns a new From tag to the new request.

  • Record-Route and Via header fields are not copied. The container adds its own Via header field to the request when it is actually sent outside the application server.

  • The headers in the new request can added to the optional headerMap, a Map of headers used in place of the ones from origRequest, for example:

    {”From” => {sip:myname@myhost.com},
    ”To” => {sip:yourname@yourhost.com} }
    

    The only headers that can be set using this headerMap are non-system headers as well as From, To, and Route headers. For Contact headers, only the user part and some parameters are to be used as defined in 5.1.3 The Contact Header Field in JSR-359, https://jcp.org/en/jsr/detail?id=359. The values in the map are defined in a java.util.Set to account for multi-valued headers.

    The values in headerMap MUST override the values in the request derived from the origRequest. Specifically, they do not append header values. Attempts to set any other system header results in an IllegalArgumentException.

  • The linked boolean flag indicates whether the ensuing SipSession and SipServletRequest are to be linked to the original ones. The concept of linking is described in "Linked SIP Sessions and Linked Request".

  • For non-REGISTER requests, the Contact header field is not copied but is populated by Converged Application Server as usual.

Like other createRequest() methods, the returned request belongs to a new SipSession.

Note:

The SipFactory.createRequest(SipServletRequest origRequest, boolean sameCallId) method has been deprecated in release 2.0 as the usage of this method with sameCallId flag set to true actually breaks the provisions of RFC 3261, https://www.ietf.org/rfc/rfc3261.txt, where the Call-ID value must be unique across dialogs. The use of B2buaHelper.createRequest(SipServletRequest origRequest) is recommended.

Linked SIP Sessions and Linked Request

This section describes using linked SIP sessions and requests in the context of B2BUAs.

Explicit Session Linkage

A B2BUA usually contains two SipSessions (although there can be more than two). The most common function of a B2BUA is to forward requests and responses from one SipSession to the other, after performing some transformation, usually an application of business logic. The B2buaHelper class simplifies the usage of this pattern by optionally linking the two SipSessions and SipServletRequests when you use it to create the new request:

SipServletRequest B2buaHelper.createRequest(SipServletRequest origRequest, boolean linked, java.util.Map<java.lang.String, java.util.Set> headerMap)

The effect of this method when the linked parameter is true is to create a new SipServletRequest using the original request, such that the two SipSessions and the two SipServletRequests are linked together. When the two SipSessions and requests are linked, you can to navigate from one to the other.

Example 9-11 shows how you can access a linked session.

Example 9-11 Accessing a Linked Session

doSuccessResponse(SipServletResponse response) {
  ....
  otherSession = B2buaHelper.getLinkedSession(response.getSession());
  // Do something on otherSession
  ....
}

The getLinkedSession() is defined in the B2buaHelper class. The helper class works like a Visitor to the SipSession (and other classes) and encapsulates functionality useful to a B2BUA implementation.

Similar to SipSessions, the linked SipServletRequest can be obtained from the method:

B2buaHelper.getLinkedSipServletRequest(SipServletRequest)

Besides the B2buaHelper.createRequest() method, the linking can also be explicitly achieved by calling:

B2buaHelper.linkSipSessions(session1, session2) throws IllegalArgumentException;

An IllegalArgumentException is thrown when sessions cannot be linked together, such as when one or both sessions have terminated, or belong to different SipApplicationSessions, or one or both have been linked to different SipSessions.

The following helper method unlinks other sessions that are linked with a session:

B2buaHelper.unLinkSipSessions(session) throws IllegalArgumentException;

Only one SipSession can be linked to another single SipSession belonging to the same SipApplicationSession.

The linkage at the SipServletRequest level is implicit whenever a new request is created based on the original with link argument as true. There is no explicit linking or unlinking of SipServletRequests.

Implicit Session Linkage

Another useful method on B2buaHelper for subsequent requests is:

B2buaHelper.createRequest(SipSession session, SipServletRequest origRequest, java.util.Map<java.lang.String, java.util.Set> headerMap) throws IllegalArgumentException

The session is the SipSession on which this subsequent request is to be sent. The origRequest is the request received on another SipSession on which this request is to be created. The headerMap can contain any non-system header which needs to be overridden in the resulting request. Any attempt to set a system header results in an IllegalArgumentException. A call to the createRequest() method also automatically links the two SipSessions, if they are not already linked, as well as the two SipServletRequests.

Access to Uncommitted Messages

The method SipServletMessage.isComitted(), defines the committed semantics for a message:

public boolean isCommitted();

SipServletRequest and SipServletResponse objects always implicitly belong to a SIP transaction. The transaction state machine, as defined by JSR-359, constrains the messages that can legally be sent at various points of processing. If a servlet attempts to send a message that violates the transaction state machine, the container throws an IllegalStateException.

A SipServletMessage is committed when one of the following conditions is true:

  • The message is an incoming request for which a final response has been generated.

  • The message is an outgoing request that was sent.

  • The message is an incoming non-reliable provisional response received by a servlet acting as a UAC.

  • The message is an incoming reliable provisional response for which a PRACK was already generated.

    Note:

    This scenario applies to containers that support the 100rel extension.
  • The message is an incoming final response received by a servlet acting as a UAC for a Non-INVITE transaction.

  • The message is a response that has been forwarded upstream.

  • The message is an incoming final response to an INVITE transaction and an ACK was generated.

  • The message is an outgoing request, the client transaction has timed out, and no response was received from the UAS and the container generates a 408 response locally.

The semantics of the committed message is that it cannot be further modified or sent in any way.

The B2BUA Helper class method, B2buaHelper.getPendingMessages() gives the application a list of uncommitted messages in the order of increasing CSeq based on the UA mode, because there may be more than one request/response uncommitted on a SipSession:

List<SipServletMessage> B2buaHelper.getPendingMessages(SipSession, UAMode);

The UAMode is an ENUM with values UAC or UAS. The same session can act as a UAC or a UAS, and the UAMode indicates messages pertaining the particular mode.

For example, consider a B2BUA involved in a typical INV-200-ACK scenario that receives an ACK on one leg and wishes to forward it to the other. The B2BUA could call B2buaHelper.getPendingMessages(leg2Session, UAMode.UAC) to retrieve the pending messages which contain the original 200 response received on the second leg. The B2BUA can then create the ACK using the SipServletResponse.createAck() method. A PRACK request can be created in a similar way from a reliable 1xx response.

Original Request and Session Cloning

The incoming request that results in the creation of a SipSession is called the original request. The application can create a response to the original request even if that request was committed and the application does not have a reference to it. That is necessary because the B2BUA application may need to send more than one successful response to a request, for example, in the case when a downstream proxy is forked and more than one success response must be forwarded upstream. A response to the original request can be made using the createResponseToOriginalRequest() method:

SipServletResponse B2buaHelper.createResponseToOriginalRequest(
                        SipSession session,
                        int status, 
                        String reasonPhrase) throws IllegalStateException;

This only works on initial requests, since only original requests require multiple responses.

The generated response must have a different To tag from the other responses generated to the request and must result in a different SipSession. In this and similar cases, Converged Application Server clones the original SipSession for the second and subsequent dialogs, as defined in 8.2.3.2 Derived SipSessions of JSR-359, https://jcp.org/en/jsr/detail?id=359. The cloned session object contains the same application data but its createRequest() method creates requests belonging to that second or subsequent dialog, that is, with a To tag specific to that dialog.

Request and Session Cloning and Linking

In the case above when more than one response is received on the UAC side, it results in a cloned UAC SipSession. When the response is sent on the UAS side using the original request, it is in context of the cloned UAS SipSession. Those SipSessions are pair-wise linked for easy navigation.

For example, if UAS-1 is the SipSession on which the incoming request was received and UAC-1 is the SipSession on which the outgoing request was relayed, then in the case of multiple 2xx responses, one response is processed by the UAC-1 SipSession. When another 2xx response is received, Converged Application Server clones the UAC-1 SipSession to create a UAC-2 SipSession to process this new response. The B2buaHelper has the getLinkedSession() method to navigate from UAS-1 to UAC-1 and back again. As for the cloned SipSession UAC-2, the B2buaHelper furnishes the UAS-2 (a clone of UAS-1) when the getLinkedSession() method is invoked with UAC-2. The applications can then create the response to the original request but now in the context of UAS-2 by invoking the method:

SipServletResponse B2buaHelper.createResponseToOriginalRequest(
             SipSession session-uas-2,
             int status, 
             String reasonPhrase) throws IllegalStateException;