SIP ACK Dialog is null

你离开我真会死。 提交于 2019-12-06 08:21:52

Let's try to be a bit more detailed.

What I've done is to take the sample UAC Code available in the JAIN SIP 1.2 download at JAIN SIP's Jenkins and modify it to cover both the REGISTER and an initial INVITE.

I've tested with an IMS core, so it might be slightly different from your experience with an Asterisk PBX, in particular I don't have to authenticate the INVITEs.

In terms of high level structure, I have:

One single class (TestUAC) that is both the main class and implements the SipListener interface.

  1. On main, I initialize the stack, setup the listeners, read any configuration, and finally I call a sendRegister() that will send the first REGISTER and initiate the process.
  2. On processResponse() callback. Based on the method of the CSeq of the response, I candistinguish between:
    • Responses to the REGISTER (processRegisterResponse())
    • Responses to the INVITE (processInviteResponse())
    • Responses to a BYE (processByeResponse())
  3. On processRequest() and the other callbacks I just have traces.

In general, I keep little information around as object attributes:

  • Stack stuff: references to sipFactory, sipProvider, sipStack, addressFactory, messageFactory, headerFactory
  • Info about my identification and who I'd like to talk to:

For example

   private String callingURI;
   private String calledURI;
   private String username;
   private String password;
   private Address fromNameAddress;
   private ContactHeader contactHeader;

Also I keep an incrementing counter for the cseq and (following the JAIN SIP example) a copy of the ackRequest to be used to reply to retransmitted 200 OK.

// Save the created ACK request, to respond to retransmitted 2xx
private Request ackRequest;

private long cseq=1L;

As I said, the processResponse() just distributes based on the CSeq method:

public void processResponse(ResponseEvent responseReceivedEvent) {
    System.out.println("Got a response");
    Response response = (Response) responseReceivedEvent.getResponse();
    CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);

    System.out.println("Response received : Status Code = "
            + response.getStatusCode() + " " + cseq);

    try {
        if(cseq.getMethod().equals(Request.REGISTER)) {
            processRegisterResponse(responseReceivedEvent);
        }
        else if(cseq.getMethod().equals(Request.INVITE)) {
            processInviteResponse(responseReceivedEvent);
        }
        else if(cseq.getMethod().equals(Request.BYE)) {
            processByeResponse(responseReceivedEvent);
        }
        else {
            System.out.println("Response to unexpected request");
        }
    } catch(Exception e)  {
        e.printStackTrace();
    }
}

The processRegisterResponse() distinguishes between the 401 Unauthorized and 200 OK responses. In the first case it will trigger sending the REGISTER with authentication. In the second case it will request to send the INVITE:

private void processRegisterResponse(ResponseEvent responseReceivedEvent) throws TransactionUnavailableException, ParseException, InvalidArgumentException, SipException, NoSuchAlgorithmException {
    Response response = (Response) responseReceivedEvent.getResponse();

    if(response.getStatusCode() == Response.UNAUTHORIZED) {
        sendRegister(response);
    }
    else if (response.getStatusCode() == Response.OK) {
        contactHeader=(ContactHeader)response.getHeader(ContactHeader.NAME);
        sendInvite();
    }               
}

Now, for the sendInvite() (I'll try to distill it to the relevant parts).

private void sendInvite() throws ParseException, InvalidArgumentException, TransactionUnavailableException, SipException {

    // create To Header
    URI toAddress = addressFactory.createURI(calledURI);
    Address toNameAddress = addressFactory.createAddress(toAddress);
    ToHeader toHeader = headerFactory.createToHeader(toNameAddress,null);

    FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, "12345");
    // Create ViaHeaders
    ArrayList<ViaHeader> viaHeaders = getViaHeaders();

    // Create a new CallId header
    CallIdHeader callIdHeader = sipProvider.getNewCallId();

    // Create a new Cseq header
    CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(cseq,Request.INVITE);
    cseq++;

    // Create a new MaxForwardsHeader
    MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);

    // Create the request.
    Request request = messageFactory.createRequest(toAddress,
            Request.INVITE, callIdHeader, cSeqHeader, fromHeader,
            toHeader, viaHeaders, maxForwards);

    request.addHeader(contactHeader);

    // at this point you should add the rest of the headers, content, etc.

    // Create the client transaction.
    ClientTransaction currentTid = sipProvider.getNewClientTransaction(request);
    // send the request out.
    currentTid.sendRequest();
}

Finally, the next thing that will happen is that we will receive the 200 OK for the INVITE. This is mostly code from the JAIN SLEE example that I've moved around into the processInviteResponse() (again I'll try to distill it to the basic).

private void processInviteResponse(ResponseEvent responseReceivedEvent) throws SipException, InvalidArgumentException {
    Response response = (Response) responseReceivedEvent.getResponse();
    ClientTransaction tid = responseReceivedEvent.getClientTransaction();
    CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME);     
    Dialog dialog = responseReceivedEvent.getDialog();

    if (tid == null) {
        // RFC3261: MUST respond to every 2xx
        if (ackRequest!=null && dialog!=null) {
            System.out.println("re-sending ACK");
            dialog.sendAck(ackRequest);
        }
        return;
    }

    if (response.getStatusCode() == Response.OK) {
        System.out.println("Dialog after 200 OK  " + dialog);
        System.out.println("Dialog State after 200 OK  " + dialog.getState());
        ackRequest = dialog.createAck(cseq.getSeqNumber() );
        System.out.println("Sending ACK");
        dialog.sendAck(ackRequest);
    }
}

After receiving the 200 OK and sending the ACK you should probably consider what to do next: for example, in the JAIN SLEE UAC example, it starts a TimerTask to send a BYE after some time.

You can add also the handling of other error (or provisional) responses there. But remember that the stack will ACK automatically (no code needed) the final error response.

For a final error response, JAIN SIP implementation should take care of sending the ACK for you. See for example at SipListener.processResponse() javadoc:

A UAC needs to send an ACK for every final Response it receives, however the procedure for sending the ACK depends on the type of Response. For final responses between 300 and 699, the ACK processing is done by the transaction layer i.e. handled by the implementation. For 2xx responses, the ACK processing is done by the UAC application, to guarantee the three way handshake of an INVITE transaction

For a 200 OK, you have to explicitly send the ACK, and the procedure will be to get the this.dialog = responseEvent.getDialog();. That dialog will have remote target and remote tag.


Updating the answer with the new problems.

  1. You were trying to send an ACK for the 200 OK of the REGISTER. This is wrong. ACK is only used when a reliable answer is required. Currently INVITE is the only SIP Method that requires it.

  2. When you send the INVITE you get a 401 Unauthorized response back. It seems that your SIP Proxy is configured to require authorization of INVITE. You solve that in the same way as with the REGISTER:

    • You send the INVITE and receive a 401 Unathorized back. Look for the WWW-Authenticate header for the challenge.
    • The JAIN SIP stack will take care of the ACK for the final response.
    • Sent the INVITE again within the same Call-ID, this time with the Authorization header, responding to the challenge.
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!