diff --git a/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java b/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java new file mode 100644 index 0000000000..be405bdb1a --- /dev/null +++ b/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java @@ -0,0 +1,116 @@ +package org.cafesip.sipunit; + +import javax.sip.header.*; +import java.util.List; + +/** + * Helper class for {@link SipPhone} registration process. Used to override the headers which will be generated + * by the REGISTER request. + *

+ * The purpose of this class is to provide an access to the underlying REGISTER request to test out different REGISTRATION + * message forms. This way a separate SIP registrar component may be forced to produce an error, allowing the external + * component to be integration tested with the desired REGISTER request header data. + *

+ * Created by TELES AG on 12/06/2017. + */ +public class HeaderConfiguration { + private ToHeader toHeader; + private FromHeader fromHeader; + + private CallIdHeader callIdHeader; + + private CSeqHeader cSeqHeader; + private MaxForwardsHeader maxForwardsHeader; + + private List viaHeaders; + private ContactHeader contactHeader; + + private ExpiresHeader expiresHeader; + + private UserAgentHeader userAgentHeader; + + /** + * If set to true, will not add the {@link ContactHeader} to the REGISTER request in {@link SipPhone} + */ + private boolean ignoreContact = false; + + public ToHeader getToHeader() { + return toHeader; + } + + public void setToHeader(ToHeader toHeader) { + this.toHeader = toHeader; + } + + public FromHeader getFromHeader() { + return fromHeader; + } + + public void setFromHeader(FromHeader fromHeader) { + this.fromHeader = fromHeader; + } + + public CallIdHeader getCallIdHeader() { + return callIdHeader; + } + + public void setCallIdHeader(CallIdHeader callIdHeader) { + this.callIdHeader = callIdHeader; + } + + public CSeqHeader getCSeqHeader() { + return cSeqHeader; + } + + public void setCSeqHeader(CSeqHeader cSeqHeader) { + this.cSeqHeader = cSeqHeader; + } + + public MaxForwardsHeader getMaxForwardsHeader() { + return maxForwardsHeader; + } + + public void setMaxForwardsHeader(MaxForwardsHeader maxForwardsHeader) { + this.maxForwardsHeader = maxForwardsHeader; + } + + public List getViaHeaders() { + return viaHeaders; + } + + public void setViaHeaders(List viaHeaders) { + this.viaHeaders = viaHeaders; + } + + public ContactHeader getContactHeader() { + return contactHeader; + } + + public void setContactHeader(ContactHeader contactHeader) { + this.contactHeader = contactHeader; + } + + public ExpiresHeader getExpiresHeader() { + return expiresHeader; + } + + public void setExpiresHeader(ExpiresHeader expiresHeader) { + this.expiresHeader = expiresHeader; + } + + public UserAgentHeader getUserAgentHeader() { + return userAgentHeader; + } + + public void setUserAgentHeader(UserAgentHeader userAgentHeader) { + this.userAgentHeader = userAgentHeader; + } + + public boolean isIgnoreContact() { + return ignoreContact; + } + + public void setIgnoreContact(boolean ignoreContact) { + this.ignoreContact = ignoreContact; + } +} diff --git a/src/main/java/org/cafesip/sipunit/SipPhone.java b/src/main/java/org/cafesip/sipunit/SipPhone.java index ed834a085f..46cb570f4f 100644 --- a/src/main/java/org/cafesip/sipunit/SipPhone.java +++ b/src/main/java/org/cafesip/sipunit/SipPhone.java @@ -20,43 +20,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.EventObject; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; - -import javax.sip.Dialog; -import javax.sip.InvalidArgumentException; -import javax.sip.RequestEvent; -import javax.sip.ResponseEvent; -import javax.sip.TimeoutEvent; +import javax.sip.*; import javax.sip.address.Address; import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.address.URI; -import javax.sip.header.AuthorizationHeader; -import javax.sip.header.CSeqHeader; -import javax.sip.header.CallIdHeader; -import javax.sip.header.ContactHeader; -import javax.sip.header.EventHeader; -import javax.sip.header.ExpiresHeader; -import javax.sip.header.FromHeader; -import javax.sip.header.Header; -import javax.sip.header.HeaderFactory; -import javax.sip.header.MaxForwardsHeader; -import javax.sip.header.ProxyAuthenticateHeader; -import javax.sip.header.ToHeader; -import javax.sip.header.ViaHeader; -import javax.sip.header.WWWAuthenticateHeader; +import javax.sip.header.*; import javax.sip.message.MessageFactory; import javax.sip.message.Request; import javax.sip.message.Response; +import java.text.ParseException; +import java.util.*; /** * This class provides a test program with User Agent (UA) access to the SIP protocol in the form of @@ -65,10 +39,10 @@ * (SUBSCRIBE/NOTIFY) operations, call refer, etc. In future, a SipPhone object can have more than * one SipCall object associated with it but currently only one is supported. Multiple subscriptions * (buddy/presence, refer) are supported per SipPhone object. - * + * *

* A SipPhone object is created by calling SipStack.createSipPhone(). - * + * *

* Many of the methods in this class return an object or true return value if successful. In case of * an error or caller-specified timeout, a null object or a false is returned. The @@ -82,9 +56,9 @@ * indicating the cause of the problem. If an exception was involved, this string will contain the * name of the Exception class and the exception message. This class has a method, format(), which * can be called to obtain a human-readable string containing all of this error information. - * + * * @author Amit Chatterjee, Becky McElroy - * + * */ public class SipPhone extends SipSession implements SipActionObject, RequestListener { @@ -95,6 +69,10 @@ public class SipPhone extends SipSession implements SipActionObject, RequestList private CSeqHeader cseq; private Request lastRegistrationRequest; + private Response lastRegistrationResponse; + + private String registrarHost; + private int registrarPort; private Hashtable credentials = new Hashtable<>(); @@ -130,6 +108,9 @@ protected SipPhone(SipStack stack, String host, String proto, int port, String m throws ParseException, InvalidArgumentException { super(stack, host, proto, port, me); this.addRequestListener(Request.NOTIFY, this); + + this.registrarHost = host; + this.registrarPort = port; } // TODO, all SipPhone creation on a per-SipProvider basis, use its @@ -139,6 +120,17 @@ protected SipPhone(SipStack stack, String host, String me) throws ParseException, InvalidArgumentException { super(stack, host, me); this.addRequestListener(Request.NOTIFY, this); + + this.registrarHost = host; + this.registrarPort = SipStack.DEFAULT_PORT; + } + + protected SipPhone(SipStack stack, String registrarHost, int registrarPort, String host, String proto, int port, String me) throws ParseException, InvalidArgumentException, NoSuchFieldException { + super(stack, host, proto, port, me); + this.addRequestListener(Request.NOTIFY, this); + + this.registrarHost = registrarHost; + this.registrarPort = registrarPort; } /** @@ -146,11 +138,11 @@ protected SipPhone(SipStack stack, String host, String me) * SipPhone's proxy host, or if no proxy was specified when this SipPhone was created, the * Request-URI address information is taken from this SipPhone's URI (address of record). For * other Request-URI alternatives, see the register() method that takes parameter requestUri. - * + * *

* Initially, a REGISTER message is sent without any user name and password. If the server returns * an OK, this method returns a true value. - * + * *

* If any challenge is received in response to sending the REGISTER message (response code * UNAUTHORIZED or PROXY_AUTHENTICATION_REQUIRED), the SipPhone's credentials list is checked @@ -167,31 +159,31 @@ protected SipPhone(SipStack stack, String host, String me) * user, password). Also, the authorization created for this registration is not saved for re-use * on a later registration. IE, the user/password parameters are for a one-time, single-shot use * only. - * + * *

* After responding to the challenge(s) by resending the REGISTER message, this method returns a * true or false value depending on the outcome as indicated by the server. - * + * *

* If the contact parameter is null, user@hostname is used where hostname is the SipStack's IP * address property which defaults to InetAddress.getLocalHost().getHostAddress(), and other * SipStack properties also apply. Otherwise, the contact parameter given is used in the * Registration message sent to the server. - * + * *

* If the expiry parameter is 0, the registration request never expires. Otherwise, the duration, * given in seconds, is sent to server. - * + * *

* This method can be called repeatedly to update the expiry or to add new contacts. - * + * *

* This method determines the contact information for this user agent, whether the registration * was successful or not. If successful, the contact information may have been updated by the * server (such as the expiry time, if not specified to this method by the caller). Once this * method has been called, the test program can get information about the contact for this agent * by calling the *MyContact*() getter methods. - * + * * @param user Optional - user name for authenticating with the server. Required if the server * issues an authentication challenge. * @param password Optional - used only if the server issues an authentication challenge. @@ -205,17 +197,40 @@ protected SipPhone(SipStack stack, String host, String me) * out why. */ public boolean register(String user, String password, String contact, int expiry, long timeout) { + return register(user, password, contact, expiry, timeout, null); + } + + /** + * This method behaves in the same way as {@link SipPhone#register(String, String, String, int, long)}, but provides + * the option to override specific headers in the REGISTER request sent to the target registrar. + * + * @param user Optional - user name for authenticating with the server. Required if the server + * issues an authentication challenge. + * @param password Optional - used only if the server issues an authentication challenge. + * @param contact An URI string (ex: sip:bob@192.0.2.4), or null to use the default contact for + * this user agent. + * @param expiry Expiry time in seconds, or 0 if no registration expiry. + * @param timeout The maximum amount of time to wait for a response, in milliseconds. Use a value + * of 0 to wait indefinitely. + * @param headerConfiguration Header builder for overrides of specific headers in the REGISTER request + * @return false if registration fails or an error is encountered, true otherwise. In case of + * false, call getErrorMessage(), getReturnCode() and/or getException() methods to find + * out why. + * + * @see SipPhone#register(String, String, String, int, long) + */ + public boolean register(String user, String password, String contact, int expiry, long timeout, HeaderConfiguration headerConfiguration) { AddressFactory addrFactory = parent.getAddressFactory(); SipURI requestUri = null; try { - if (proxyHost != null) { - requestUri = addrFactory.createSipURI(null, proxyHost); - requestUri.setPort(proxyPort); + if (registrarHost != null) { + requestUri = addrFactory.createSipURI(null, registrarHost); + requestUri.setPort(registrarPort); requestUri.setTransportParam(proxyProto); } - return register(requestUri, user, password, contact, expiry, timeout); + return register(requestUri, user, password, contact, expiry, timeout, headerConfiguration); } catch (Exception ex) { setReturnCode(EXCEPTION_ENCOUNTERED); setException(ex); @@ -231,131 +246,196 @@ public boolean register(String user, String password, String contact, int expiry * is derived from this SipPhone's URI, or address of record (for example, if this SipPhone's * address of record is "sip:amit@cafesip.org", the REGISTER Request-URI will be sip:cafesip.org). * Otherwise, the requestUri passed in is used for the REGISTER Request-URI. - * */ public boolean register(SipURI requestUri, String user, String password, String contact, - int expiry, long timeout) { - initErrorInfo(); - - try { - AddressFactory addr_factory = parent.getAddressFactory(); - HeaderFactory hdr_factory = parent.getHeaderFactory(); - - if (requestUri == null) { - requestUri = addr_factory.createSipURI(null, ((SipURI) (myAddress.getURI())).getHost()); - requestUri.setPort(((SipURI) (myAddress.getURI())).getPort()); - if (((SipURI) (myAddress.getURI())).getTransportParam() != null) { - requestUri.setTransportParam(((SipURI) (myAddress.getURI())).getTransportParam()); - } - } - - String method = Request.REGISTER; - - ToHeader to_header = hdr_factory.createToHeader(myAddress, null); - FromHeader from_header = hdr_factory.createFromHeader(myAddress, generateNewTag()); - - CallIdHeader callid_header = hdr_factory.createCallIdHeader(myRegistrationId); - - cseq = hdr_factory.createCSeqHeader(cseq == null ? 1 : (cseq.getSeqNumber() + 1), method); - - MaxForwardsHeader max_forwards = hdr_factory.createMaxForwardsHeader(MAX_FORWARDS_DEFAULT); - - if (contact != null) { - URI uri = addr_factory.createURI(contact); - if (uri.isSipURI() == false) { - setReturnCode(INVALID_ARGUMENT); - setErrorMessage("URI " + contact + " is not a Sip URI"); - return false; - } - - Address contact_address = addr_factory.createAddress(uri); - ContactHeader hdr = hdr_factory.createContactHeader(contact_address); - hdr.setExpires(expiry); - - synchronized (contactLock) { - contactInfo = new SipContact(); - contactInfo.setContactHeader(hdr); - } - } - - List via_headers = getViaHeaders(); - - Request msg = parent.getMessageFactory().createRequest(requestUri, method, callid_header, - cseq, from_header, to_header, via_headers, max_forwards); - - msg.addHeader(contactInfo.getContactHeader()); // use - // setHeader()? - - if (expiry > 0) { - ExpiresHeader expires = hdr_factory.createExpiresHeader(expiry); - msg.setExpires(expires); - } - - // include any auth information for this User Agent's registration - // if any exists - - Map auth_list = - getAuthorizations().get(myRegistrationId); - if (auth_list != null) { - List auth_headers = - new ArrayList<>(auth_list.values()); - Iterator i = auth_headers.iterator(); - while (i.hasNext()) { - AuthorizationHeader auth = i.next(); - msg.addHeader(auth); - } - } else { - // create the auth list entry for this phone's registrations - enableAuthorization(myRegistrationId); - } - - // send the REGISTRATION request and get the response - Response response = sendRegistrationMessage(msg, user, password, timeout); - if (response == null) { - return false; - } + int expiry, long timeout) { + return register(requestUri, user, password, contact, expiry, timeout, null); + } - // update our contact info with that of the server response - - // server may have reset our contact expiry - - ListIterator contacts = response.getHeaders(ContactHeader.NAME); - if (contacts != null) { - while (contacts.hasNext()) { - // TODO - at some point save ALL the contact headers and - // provide a getter for the list of SipContact objects - // (gobalContactList). - // dispose() and unregister() can use the list of contact - // headers. - // for now just save this agent's info - - ContactHeader hdr = (ContactHeader) contacts.next(); - if (hdr.getAddress().getURI().toString().equals(contactInfo.getURI()) == true) { - contactInfo.setContactHeader(hdr); - break; - } - } - } + /** + * This method is the same as the register(String user, String password, String contact, int + * expiry, long timeout) method except for the Request-URI used in the outgoing REGISTER method. + * If the requestUri parameter passed into this method is null, the Request-URI for the REGISTER + * is derived from this SipPhone's URI, or address of record (for example, if this SipPhone's + * address of record is "sip:amit@cafesip.org", the REGISTER Request-URI will be sip:cafesip.org). + * Otherwise, the requestUri passed in is used for the REGISTER Request-URI. + *

+ * Supports overriding of headers using the {@link HeaderConfiguration} object. HeaderConfiguration may be null in + * case no headers need to be overridden, providing normalized behavior described in {@link SipPhone#register( + * SipURI, String, String, String, int, long)}. + */ + public boolean register(SipURI requestUri, String user, String password, String contact, + int expiry, long timeout, HeaderConfiguration headerConfiguration) { + initErrorInfo(); + + if (headerConfiguration == null) { + headerConfiguration = new HeaderConfiguration(); + } + + try { + AddressFactory addr_factory = parent.getAddressFactory(); + HeaderFactory hdr_factory = parent.getHeaderFactory(); + + if (requestUri == null) { + requestUri = addr_factory.createSipURI(null, ((SipURI) (myAddress.getURI())).getHost()); + requestUri.setPort(((SipURI) (myAddress.getURI())).getPort()); + if (((SipURI) (myAddress.getURI())).getTransportParam() != null) { + requestUri.setTransportParam(((SipURI) (myAddress.getURI())).getTransportParam()); + } + } + + String method = Request.REGISTER; + + if (headerConfiguration.getToHeader() == null) { + headerConfiguration.setToHeader(hdr_factory.createToHeader(myAddress, null)); + } + if (headerConfiguration.getFromHeader() == null) { + headerConfiguration.setFromHeader(hdr_factory.createFromHeader(myAddress, generateNewTag())); + } + + + if (headerConfiguration.getCallIdHeader() == null) { + headerConfiguration.setCallIdHeader(hdr_factory.createCallIdHeader(myRegistrationId)); + } + + if (headerConfiguration.getCSeqHeader() == null) { + cseq = hdr_factory.createCSeqHeader(cseq == null ? 1 : (cseq.getSeqNumber() + 1), method); + headerConfiguration.setCSeqHeader(cseq); + } + + if (headerConfiguration.getMaxForwardsHeader() == null) { + headerConfiguration.setMaxForwardsHeader(hdr_factory.createMaxForwardsHeader(MAX_FORWARDS_DEFAULT)); + } + + if (contact != null) { + URI uri = addr_factory.createURI(contact); + if (uri.isSipURI() == false) { + setReturnCode(INVALID_ARGUMENT); + setErrorMessage("URI " + contact + " is not a Sip URI"); + return false; + } + + Address contact_address = addr_factory.createAddress(uri); + ContactHeader hdr = hdr_factory.createContactHeader(contact_address); + hdr.setExpires(expiry); + + synchronized (contactLock) { + SipContact newContactInfo = new SipContact(); + newContactInfo.setContactHeader(hdr); + + contactInfo = newContactInfo; + } + } + + if (headerConfiguration.getViaHeaders() == null) { + headerConfiguration.setViaHeaders(getViaHeaders()); + } + + Request msg = parent.getMessageFactory().createRequest(requestUri, method, headerConfiguration.getCallIdHeader(), + headerConfiguration.getCSeqHeader(), headerConfiguration.getFromHeader(), headerConfiguration.getToHeader(), + headerConfiguration.getViaHeaders(), headerConfiguration.getMaxForwardsHeader()); + + if (!headerConfiguration.isIgnoreContact()) { + if (headerConfiguration.getContactHeader() == null) { + msg.addHeader(contactInfo.getContactHeader()); // use + // setHeader()? + } else { + msg.addHeader(headerConfiguration.getContactHeader()); + } + } + + if (headerConfiguration.getExpiresHeader() == null) { + if (expiry > 0) { + ExpiresHeader expires = hdr_factory.createExpiresHeader(expiry); + msg.setExpires(expires); + } + } else { + msg.setExpires(headerConfiguration.getExpiresHeader()); + } + + if (headerConfiguration.getUserAgentHeader() != null) { + msg.addHeader(headerConfiguration.getUserAgentHeader()); + } + + // include any auth information for this User Agent's registration + // if any exists + + Map auth_list = + getAuthorizations().get(myRegistrationId); + if (auth_list != null) { + List auth_headers = + new ArrayList<>(auth_list.values()); + Iterator i = auth_headers.iterator(); + while (i.hasNext()) { + AuthorizationHeader auth = i.next(); + msg.addHeader(auth); + } + } else { + // create the auth list entry for this phone's registrations + enableAuthorization(myRegistrationId); + } + + // send the REGISTRATION request and get the response + Response response = sendRegistrationMessage(msg, user, password, timeout); + if (response == null) { + return false; + } + + // update our contact info with that of the server response - + // server may have reset our contact expiry + + ListIterator contacts = response.getHeaders(ContactHeader.NAME); + if (contacts != null) { + while (contacts.hasNext()) { + // TODO - at some point save ALL the contact headers and + // provide a getter for the list of SipContact objects + // (gobalContactList). + // dispose() and unregister() can use the list of contact + // headers. + // for now just save this agent's info + + ContactHeader hdr = (ContactHeader) contacts.next(); + if (hdr.getAddress().getURI().toString().equals(contactInfo.getURI()) == true) { + ((SipContact) contactInfo).setContactHeader(hdr); + break; + } + } + } + + return true; + } catch (Exception ex) { + setReturnCode(EXCEPTION_ENCOUNTERED); + setException(ex); + setErrorMessage("Exception: " + ex.getClass().getName() + ": " + ex.getMessage()); + return false; + } + } - return true; - } catch (Exception ex) { - setReturnCode(EXCEPTION_ENCOUNTERED); - setException(ex); - setErrorMessage("Exception: " + ex.getClass().getName() + ": " + ex.getMessage()); - return false; - } + /** + * This method is equivalent to the register(String user, String password, String contact, int + * expiry, long timeout) method except with no authorization parameters specified. Call this + * method if no authorization will be needed or after setting up the SipPhone's credentials list. + * + * @param contact An URI string (ex: sip:bob@192.0.2.4) + * @param expiry Expiry time in seconds, or 0 if no expiry. + * @return false if registration fails or an error is encountered, true otherwise. + */ + public boolean register(String contact, int expiry) { + return register(null, null, contact, expiry, 0, null); } /** * This method is equivalent to the register(String user, String password, String contact, int * expiry, long timeout) method except with no authorization parameters specified. Call this * method if no authorization will be needed or after setting up the SipPhone's credentials list. - * - * @param contact An URI string (ex: sip:bob@192.0.2.4) - * @param expiry Expiry time in seconds, or 0 if no expiry. + * + * @param contact An URI string (ex: sip:bob@192.0.2.4) + * @param expiry Expiry time in seconds, or 0 if no expiry. + * @param headerConfiguration Header override configuration * @return false if registration fails or an error is encountered, true otherwise. */ - public boolean register(String contact, int expiry) { - return register(null, null, contact, expiry, 0); + public boolean register(String contact, int expiry, HeaderConfiguration headerConfiguration) { + return register(null, null, contact, expiry, 0, headerConfiguration); } /** @@ -363,16 +443,15 @@ public boolean register(String contact, int expiry) { * successful or no unregistration was needed, and false otherwise. Any authorization headers * required for the last registration are cleared out. If there was no previous registration, this * method does not send any messages. - * + *

*

* If the contact parameter is null, user@hostname is unregistered where hostname is obtained by * calling InetAddr.getLocalHost(). Otherwise, the contact parameter value is used in the * unregistration message sent to the server. - * + * * @param contact The contact URI (ex: sip:bob@192.0.2.4) to unregister or "*". * @param timeout The maximum amount of time to wait for a response, in milliseconds. Use a value - * of 0 to wait indefinitely. - * + * of 0 to wait indefinitely. * @return true if the unregistration succeeded or no unregistration was needed, false otherwise. */ public boolean unregister(String contact, long timeout) { @@ -437,7 +516,7 @@ public boolean unregister(String contact, long timeout) { } private Response sendRegistrationMessage(Request msg, String user, String password, - long timeout) { + long timeout) { SipTransaction trans = sendRequestWithTransaction(msg, false, null); // get the response @@ -456,6 +535,7 @@ private Response sendRegistrationMessage(Request msg, String user, String passwo Response response = ((ResponseEvent) response_event).getResponse(); int status_code = response.getStatusCode(); + lastRegistrationResponse = response; while (status_code != Response.OK) { if (status_code == Response.TRYING) { @@ -474,9 +554,10 @@ private Response sendRegistrationMessage(Request msg, String user, String passwo response = ((ResponseEvent) response_event).getResponse(); status_code = response.getStatusCode(); + lastRegistrationResponse = response; continue; } else if ((status_code == Response.UNAUTHORIZED) - || (status_code == Response.PROXY_AUTHENTICATION_REQUIRED)) { + || (status_code == Response.PROXY_AUTHENTICATION_REQUIRED)) { // modify the request to include user authorization info msg = processAuthChallenge(response, msg, user, password); @@ -516,11 +597,12 @@ private Response sendRegistrationMessage(Request msg, String user, String passwo response = ((ResponseEvent) response_event).getResponse(); status_code = response.getStatusCode(); + lastRegistrationResponse = response; continue; } else { setReturnCode(status_code); setErrorMessage( - "An unsuccessful or error status code was received from the server: " + status_code); + "An unsuccessful or error status code was received from the server: " + status_code); return null; } } @@ -534,13 +616,13 @@ private Response sendRegistrationMessage(Request msg, String user, String passwo * This method is public for test purposes and for use when using low level SipSession methods for * sending/receiving messages. A test program using high level SipUnit doesn't need to call this * method. - * + *

*

* This method modifies the given request to include the authorization header(s) required by the * given response. It may cache in SipPhone's authorizations list the AuthorizationHeader(s) * created here for use later. The modified Request object is returned, or null in case of error * or unresolved challenge. - * + *

*

* For each received challenge present in the response message: SipPhone's credentials list is * checked first, for the realm entry. If it is not found there, the username parameter passed @@ -550,17 +632,17 @@ private Response sendRegistrationMessage(Request msg, String user, String passwo * and the authorization created here is NOT saved for later re-use. If the credentials list * contains an entry for the challenging realm, then the authorization created here is saved in * the authorizations list for later re-use. - * + * * @param response the challenge that was received - * @param req_msg the request originally sent (that was challenged) + * @param req_msg the request originally sent (that was challenged) * @param username see above * @param password see above * @return the original request with the authorization header(s) added, or null if username and - * password weren't passed in and the SipPhone's credentials list doesn't have an entry - * for the realm. + * password weren't passed in and the SipPhone's credentials list doesn't have an entry + * for the realm. */ public Request processAuthChallenge(Response response, Request req_msg, String username, - String password) { + String password) { initErrorInfo(); ListIterator challenges = null; @@ -573,14 +655,14 @@ public Request processAuthChallenge(Response response, Request req_msg, String u if (challenges == null) { setReturnCode(ERROR_OF_UNKNOWN_ORIGIN); setErrorMessage( - "Improper use of processAuthChallenge() or response auth challenge header is missing"); + "Improper use of processAuthChallenge() or response auth challenge header is missing"); return null; } // find the list of cached AuthorizationHeaders for this call (Call-ID) String call_id = ((CallIdHeader) req_msg.getHeader(CallIdHeader.NAME)).getCallId(); Map authorization_list = - getAuthorizations().get(call_id); + getAuthorizations().get(call_id); if (authorization_list == null) { // it should have been created already when sent or received 1st @@ -588,7 +670,7 @@ public Request processAuthChallenge(Response response, Request req_msg, String u // call ID setReturnCode(ERROR_OF_UNKNOWN_ORIGIN); setErrorMessage( - "Invalid Call-ID header in request or the call's authorization list wasn't created"); + "Invalid Call-ID header in request or the call's authorization list wasn't created"); return null; } @@ -599,7 +681,7 @@ public Request processAuthChallenge(Response response, Request req_msg, String u } catch (Exception ex) { setReturnCode(EXCEPTION_ENCOUNTERED); setErrorMessage( - "Exception while cloning request: " + ex.getClass().getName() + ": " + ex.getMessage()); + "Exception while cloning request: " + ex.getClass().getName() + ": " + ex.getMessage()); setException(ex); return null; } @@ -636,7 +718,7 @@ public Request processAuthChallenge(Response response, Request req_msg, String u try { AuthorizationHeader authorization = getAuthorization(msg.getMethod(), - msg.getRequestURI().toString(), req_body, authenticate_header, uname, passwd); + msg.getRequestURI().toString(), req_body, authenticate_header, uname, passwd); // what was wrong with req_body = msg.getContent() == null ? "" // : msg.getContent() @@ -662,26 +744,26 @@ public Request processAuthChallenge(Response response, Request req_msg, String u /* * here's replace code - * + * * ListIterator msg_headers; if (authorization instanceof ProxyAuthorizationHeader) { * msg_headers = msg.getHeaders(ProxyAuthorizationHeader.NAME); } else { msg_headers = * msg.getHeaders(AuthorizationHeader.NAME); } - * + * * boolean replaced = false; - * - * + * + * * while (msg_headers.hasNext()) { AuthorizationHeader msg_hdr = (AuthorizationHeader) * msg_headers .next(); if (msg_hdr.getRealm().equals(realm)) { * msg_headers.set(authorization); replaced = true; break; } } - * - * + * + * * if (replaced == false) { msg.addHeader(authorization); // how to bubble auth up - // * check 1.2 API } */ } catch (Exception ex) { setReturnCode(EXCEPTION_ENCOUNTERED); setErrorMessage("Exception while creating AuthorizationHeader(s): " - + ex.getClass().getName() + ": " + ex.getMessage()); + + ex.getClass().getName() + ": " + ex.getMessage()); setException(ex); return null; } @@ -713,7 +795,7 @@ public Request processAuthChallenge(Response response, Request req_msg) { * only one SipCall object is supported per SipPhone. In future, when more than one SipCall per * SipPhone is supported, this method can be called multiple times to create multiple call legs on * the same SipPhone object. - * + * * @return A SipCall object unless an error is encountered. */ public SipCall createSipCall() { @@ -735,19 +817,19 @@ protected void dropCall(SipCall call) { * INVITE response status code is received. The object returned is a SipCall object representing * the outgoing call leg; that is, the UAC originating a call to the network. Then you can take * subsequent action on the call by making method calls on the SipCall object. - * + * *

* Use this method when (1) you want to establish a call without worrying about the details and * (2) your test program doesn't need to do anything else (ie, it can be blocked) until the * response code parameter passed to this method is received from the network. - * + * *

* In case the first condition above is false: If you need to see the (intermediate/provisional) * response messages as they come in, then use SipPhone.createSipCall() and * SipCall.initiateOutgoingCall() instead of this method. If your test program can tolerate being * blocked until the desired response is received, you can still use this method and later look * back at all the received responses by calling SipCall.getAllReceivedResponses(). - * + * *

* In case the second condition above is false: If your test code is handling both sides of the * call, or it has to do other things while this call establishment is in progress, then this @@ -755,8 +837,8 @@ protected void dropCall(SipCall call) { * returns a SipCall object after the INVITE has been successfully sent. Then, later on you can * check back with the SipCall object to see the call progress or block on the call establishment, * at a more convenient time. - * - * + * + * * @param to The URI string (ex: sip:bob@nist.gov) to which the call should be directed * @param response The SipResponse status code to look for after sending the INVITE. This method * returns when that status code is received. @@ -777,10 +859,10 @@ public SipCall makeCall(String to, int response, long timeout, String viaNonProx * This method is the same as the basic blocking makeCall() method except that it allows the * caller to specify a message body and/or additional message headers to add to or replace in the * outbound message without requiring knowledge of the JAIN-SIP API. - * + * *

* The extra parameters supported by this method are: - * + * * @param body A String to be used as the body of the message. Parameters contentType, * contentSubType must both be non-null to get the body included in the message. Use null * for no body bytes. @@ -806,7 +888,7 @@ public SipCall makeCall(String to, int response, long timeout, String viaNonProx * occur if your headers are not syntactically correct or contain nonsensical values (the * message may not pass through the local SIP stack). Use null for no replacement of * message headers. - * + * */ public SipCall makeCall(String to, int response, long timeout, String viaNonProxyRoute, String body, String contentType, String contentSubType, ArrayList additionalHeaders, @@ -827,10 +909,10 @@ public SipCall makeCall(String to, int response, long timeout, String viaNonProx * caller to specify a message body and/or additional JAIN-SIP API message headers to add to or * replace in the outbound INVITE message. Use of this method requires knowledge of the JAIN-SIP * API. - * + * *

* The extra parameters supported by this method are: - * + * * @param additionalHeaders ArrayList of javax.sip.header.Header, each element a SIP header to add * to the outbound message. These headers are added to the message after a correct message * has been constructed. Note that if you try to add a header that there is only supposed @@ -924,7 +1006,7 @@ public SipCall makeCall(String to, int response, long timeout, String viaNonProx * etc.) are automatically collected and any received authentication challenges are automatically * handled as well. The object returned by this method is a SipCall object representing the * outgoing call leg; that is, the UAC originating a call to the network. - * + * *

* After calling this method, you can later call one or more of the following methods on the * returned SipCall object to see what happened (each is nonblocking unless otherwise noted): @@ -937,20 +1019,20 @@ public SipCall makeCall(String to, int response, long timeout, String viaNonProx * waitOutgoingCallResponse() - BLOCKING - when your test program is done with its tasks and can * be blocked until the next response is received (if you are interested in something other than * OK) - use this only if you know that the INVITE transaction is still up. - * + * *

* Call this method when (1) you want to establish a call without worrying about the details and * (2) your test program needs to do other tasks after the INVITE is sent but before a * final/expected response is received (ie, the calling program cannot be blocked during call * establishment). - * + * *

* Otherwise: If you need to see or act on any of the (intermediate/provisional) response messages * as they come in, use SipPhone.createSipCall() and SipCall.initiateOutgoingCall() instead of * this method. If your test program doesn't need to do anything else until the call is * established: use the other SipPhone.makeCall() method which conveniently blocks until the * response code you specify is received from the network. - * + * * @param to The URI string (ex: sip:bob@nist.gov) to which the call should be directed * @param viaNonProxyRoute Indicates whether to route the INVITE via Proxy or some other route. If * null, route the call to the Proxy that was specified when the SipPhone object was @@ -958,7 +1040,7 @@ public SipCall makeCall(String to, int response, long timeout, String viaNonProx * as "hostaddress:port/transport" i.e. 129.1.22.333:5060/UDP. * @return A SipCall object representing the outgoing call leg, or null if an error was * encountered. - * + * */ public SipCall makeCall(String to, String viaNonProxyRoute) { return makeCall(to, viaNonProxyRoute, null, null, null); @@ -969,10 +1051,10 @@ public SipCall makeCall(String to, String viaNonProxyRoute) { * caller to specify a message body and/or additional JAIN-SIP API message headers to add to or * replace in the outbound INVITE message. Use of this method requires knowledge of the JAIN-SIP * API. - * + * *

* The extra parameters supported by this method are: - * + * * @param additionalHeaders ArrayList of javax.sip.header.Header, each element a SIP header to add * to the outbound message. These headers are added to the message after a correct message * has been constructed. Note that if you try to add a header that there is only supposed @@ -1009,9 +1091,9 @@ public SipCall makeCall(String to, String viaNonProxyRoute, ArrayList

ad * This method is the same as the basic nonblocking makeCall() method except that it allows the * caller to specify a message body and/or additional message headers to add to or replace in the * outbound message without requiring knowledge of the JAIN-SIP API. - * + * * The extra parameters supported by this method are: - * + * * @param body A String to be used as the body of the message. Parameters contentType, * contentSubType must both be non-null to get the body included in the message. Use null * for no body bytes. @@ -1037,7 +1119,7 @@ public SipCall makeCall(String to, String viaNonProxyRoute, ArrayList
ad * occur if your headers are not syntactically correct or contain nonsensical values (the * message may not pass through the local SIP stack). Use null for no replacement of * message headers. - * + * */ public SipCall makeCall(String to, String viaNonProxyRoute, String body, String contentType, String contentSubType, ArrayList additionalHeaders, @@ -1058,7 +1140,7 @@ public SipCall makeCall(String to, String viaNonProxyRoute, String body, String * nor its SipSession base class should be used again after calling the dispose() method. * Server/proxy unregistration occurs and SipCall(s) associated with this SipPhone are dropped. No * un-SUBSCRIBE is done for active Subscriptions in the buddy list. - * + * * @see org.cafesip.sipunit.SipCall#dispose() */ public void dispose() { @@ -1106,7 +1188,7 @@ protected CallIdHeader getNewCallIdHeader() * agent. This may be the value associated with the last registration attempt or as defaulted to * user@host if no registration has occurred. Or, if the setPublicAddress() has been called on * this object, the returned value will reflect the most recent call to setPublicAddress(). - * + * * @return The SipContact object currently in effect for this user agent */ public SipContact getContactInfo() { @@ -1115,12 +1197,12 @@ public SipContact getContactInfo() { /** * This method is the same as getContactInfo(). - * + * * @deprecated Use getContactInfo() instead of this method, the term 'local' in the method name is * misleading if the SipUnit test is running behind a NAT. - * + * * @return The SipContact object currently in effect for this user agent - * + * */ public SipContact getLocalContactInfo() { return getContactInfo(); @@ -1136,7 +1218,7 @@ protected void updateContactInfo(ContactHeader hdr) { /** * Gets the user Address for this SipPhone. This is the same address used in the * "from" header field. - * + * * @return Returns the javax.sip.address.Address for this SipPhone (UA). */ public Address getAddress() { @@ -1145,13 +1227,22 @@ public Address getAddress() { /** * Gets the request sent at the last successful registration. - * + * * @return Returns the lastRegistrationRequest. */ - protected Request getLastRegistrationRequest() { + public Request getLastRegistrationRequest() { return lastRegistrationRequest; } + /** + * Gets the response of the last registration attempt + * + * @return Returns the lastRegistrationResponse. + */ + public Response getLastRegistrationResponse() { + return lastRegistrationResponse; + } + /** * @return Returns the authorizations. */ @@ -1183,7 +1274,7 @@ protected void addAuthorizations(String call_id, Request msg) { /** * This method adds a new credential to the credentials list or updates an existing credential in * the list. - * + * * @param c the credential to be added/updated. */ public void addUpdateCredential(Credential c) { @@ -1192,7 +1283,7 @@ public void addUpdateCredential(Credential c) { /** * This method removes a credential from the credentials list. - * + * * @param c the credential to be removed. */ public void removeCredential(Credential c) { @@ -1201,7 +1292,7 @@ public void removeCredential(Credential c) { /** * This method removes a credential from the credentials list. - * + * * @param realm the realm associated with the credential to be removed. */ public void removeCredential(String realm) { @@ -1327,17 +1418,17 @@ private void distributeEventError(String err) * tracking the buddy's presence information. Please read the SipUnit User Guide webpage Event * Subscription (at least the operation overview part) for information on how to use SipUnit * presence capabilities. - * + * *

* This method creates a SUBSCRIBE request message, sends it out, and waits for a response to be * received. It saves the received response and checks for a "proceedable" (positive) status code * value. Positive response status codes include any of the following: provisional (status / 100 * == 1), UNAUTHORIZED, PROXY_AUTHENTICATION_REQUIRED, OK and ACCEPTED. Any other status code, or * a response timeout or any other error, is considered fatal to the subscription. - * + * *

* This method blocks until one of the above outcomes is reached. - * + * *

* In the case of a positive response status code, this method returns a PresenceSubscriber object * that will represent the buddy for the life of the subscription and puts the PresenceSubscriber @@ -1348,14 +1439,14 @@ private void distributeEventError(String err) * details at any given time such as the subscription state, amount of time left on the * subscription, termination reason, presence information, details of received responses and * requests, etc. - * + * *

* In the case of a positive response status code (a non-null object is returned), you may find * out more about the response that was just received by calling the PresenceSubscriber methods * getReturnCode() and getCurrentResponse()/getLastReceivedResponse(). Your next step at this * point will be to call the PresenceSubscriber's processResponse() method to proceed with the * SUBSCRIBE processing. - * + * *

* In the case of a fatal outcome, no subscription object is created and null is returned. In this * case, call the usual SipUnit failed-operation methods to find out what happened (ie, call this @@ -1363,7 +1454,7 @@ private void distributeEventError(String err) * getReturnCode() method will tell you the response status code that was received from the * network (unless it is an internal SipUnit error code, see the SipSession javadoc for more on * that). - * + * * @param uri the URI (ie, sip:bob@nist.gov) of the buddy to be added to the list. * @param duration the duration in seconds to put in the SUBSCRIBE message. If 0, this is * equivalent to a fetch except that the buddy stays in the buddy list even though the @@ -1425,7 +1516,7 @@ public PresenceSubscriber addBuddy(String uri, int duration, String eventId, lon * This method is the same as addBuddy(uri, duration, eventId, timeout) except that the duration * is defaulted to the default period defined in the event package RFC (3600 seconds) and no event * "id" parameter will be included. - * + * * @param uri the URI (ie, sip:bob@nist.gov) of the buddy to be added to the list. * @param timeout The maximum amount of time to wait for a SUBSCRIBE response, in milliseconds. * Use a value of 0 to wait indefinitely. @@ -1439,7 +1530,7 @@ public PresenceSubscriber addBuddy(String uri, long timeout) { /** * This method is the same as addBuddy(uri, duration, eventId, timeout) except that no event "id" * parameter will be included. - * + * * @param uri the URI (ie, sip:bob@nist.gov) of the buddy to be added to the list. * @param duration the duration in seconds to put in the SUBSCRIBE message. If 0, this is * equivalent to a fetch except that the buddy stays in the buddy list even though the @@ -1456,7 +1547,7 @@ public PresenceSubscriber addBuddy(String uri, int duration, long timeout) { /** * This method is the same as addBuddy(uri, duration, eventId, timeout) except that the duration * is defaulted to the default period defined in the event package RFC (3600 seconds). - * + * * @param uri the URI (ie, sip:bob@nist.gov) of the buddy to be added to the list. * @param eventId the event "id" to use in the SUBSCRIBE message, or null for no event "id" * parameter. See addBuddy(uri, duration, eventId, timeout) javadoc for details on event @@ -1475,7 +1566,7 @@ public PresenceSubscriber addBuddy(String uri, String eventId, long timeout) { * user's presence status and information. Please read the SipUnit User Guide webpage Event * Subscription (at least the operation overview part) for information on how to use SipUnit * presence capabilities. - * + * *

* This method creates a SUBSCRIBE request message with expiry time of 0, sends it out, and waits * for a response to be received. It saves the received response and checks for a "proceedable" @@ -1483,10 +1574,10 @@ public PresenceSubscriber addBuddy(String uri, String eventId, long timeout) { * provisional (status / 100 == 1), UNAUTHORIZED, PROXY_AUTHENTICATION_REQUIRED, OK and ACCEPTED. * Any other status code, or a response timeout or any other error, is considered fatal to the * operation. - * + * *

* This method blocks until one of the above outcomes is reached. - * + * *

* In the case of a positive response status code, this method returns a PresenceSubscriber object * representing the user and puts the object in this SipPhone's retired buddy list. The retired @@ -1496,14 +1587,14 @@ public PresenceSubscriber addBuddy(String uri, String eventId, long timeout) { * PresenceSubscriber object to proceed through the remainder of the SUBSCRIBE-NOTIFY sequence and * to find out details such as the subscription state, termination reason, presence information, * details of received responses and requests, etc. - * + * *

* In the case of a positive response status code (a non-null object is returned), you may find * out more about the response that was just received by calling the PresenceSubscriber methods * getReturnCode() and getCurrentResponse()/getLastReceivedResponse(). Your next step at this * point will be to call the PresenceSubscriber's processResponse() method to proceed with the * SUBSCRIBE processing. - * + * *

* In the case of a fatal outcome, no Subscription object is created and null is returned. In this * case, call the usual SipUnit failed-operation methods to find out what happened (ie, call this @@ -1511,7 +1602,7 @@ public PresenceSubscriber addBuddy(String uri, String eventId, long timeout) { * getReturnCode() method will tell you the response status code that was received from the * network (unless it is an internal SipUnit error code, see the SipSession javadoc for more on * that). - * + * * @param uri the URI (ie, sip:bob@nist.gov) of the user whose presence info is to be fetched. * @param eventId the event "id" to use in the SUBSCRIBE message, or null for no event "id" * parameter. Whatever is indicated here will be used subsequently, for error checking the @@ -1573,7 +1664,7 @@ public PresenceSubscriber fetchPresenceInfo(String uri, String eventId, long tim * This method is the same as fetchPresenceInfo(uri, eventId, timeout) except that no event "id" * parameter will be included in the SUBSCRIBE message. When error checking the SUBSCRIBE response * and NOTIFY from the server, no event "id" parameter will be expected. - * + * * @param uri the URI (ie, sip:bob@nist.gov) of the user whose presence info is to be fetched. * @param timeout The maximum amount of time to wait for a SUBSCRIBE response, in milliseconds. * Use a value of 0 to wait indefinitely. @@ -1592,13 +1683,13 @@ public PresenceSubscriber fetchPresenceInfo(String uri, long timeout) { * from the returned object. The user may have been a buddy in the buddy list (but was removed * from the list by the test program), or fetchPresenceInfo() was previously called for the user * to get a one-time status report, or the user may still be in the buddy list. - * + * * @param uri the URI (ie, sip:bob@nist.gov) of the user whose subscription object is to be * returned. * @return A PresenceSubscriber object that contains information about the user's last obtained * presence status and other info, or null if there was never any status fetch done for * this user and this user was never in the buddy list. - * + * */ public PresenceSubscriber getBuddyInfo(String uri) { synchronized (buddyList) { @@ -1618,10 +1709,10 @@ public PresenceSubscriber getBuddyInfo(String uri) { * are still in the buddy list. A given buddy, or subscription, in this list may be active or not * - ie, subscription termination by the far end does not remove a buddy from this list. Buddies * are removed from the list only by the test program (by calling removeBuddy()). - * + * *

* See related methods getBuddyInfo(), getRetiredBuddies(). - * + * * @return a Hashtable of zero or more entries, where the key = URI of the buddy, value = * PresenceSubscriber object. */ @@ -1635,10 +1726,10 @@ public Map getBuddyList() { * test program removes a buddy from the buddy list. The main purpose of this list is so the last * known presence status of a user can be obtained anytime. This is required to make the fetch * case useful. - * + * *

* See related methods getBuddyInfo(), getBuddyList(). - * + * * @return a Hashtable of zero or more entries, where the key = URI of the user, value = * PresenceSubscriber object. */ @@ -1672,12 +1763,12 @@ protected boolean removeRefer(ReferSubscriber ref) { * Gets the subscription object(s) associated with the given referToUri. The returned object(s) * contains subscription state, received requests (NOTIFY's) and REFER/SUBSCRIBE responses, etc. * for outbound REFER subscription(s) associated with the referToUri. - * + * * @param referToUri the referToUri that was previously passed in to SipPhone.refer(). See javadoc * there. * @return ReferSubscriber object(s) associated with the referToUri, or an empty list if there was * never a call to SipPhone.refer() with that referToUri. - * + * */ public List getRefererInfo(SipURI referToUri) { List list = new ArrayList<>(); @@ -1697,11 +1788,11 @@ public List getRefererInfo(SipURI referToUri) { * Gets the subscription object(s) associated with the given dialog ID. The * returned object(s) contains subscription state, received requests (NOTIFY's) and * REFER/SUBSCRIBE responses, etc. for outbound REFER subscription(s) associated with the dialog. - * + * * @param dialogId the dialog ID of interest * @return ReferSubscriber object(s) associated with the dialog, or an empty list if there was * never a refer subscription associated with that dialog. - * + * */ public List getRefererInfoByDialog(String dialogId) { List list = new ArrayList<>(); @@ -1723,10 +1814,10 @@ public List getRefererInfoByDialog(String dialogId) { * lifetime of this SipPhone object. A given subscription in the list may be active or not - * subscription termination does not automatically remove a subscription from this list (calling * ReferSubscriber.dispose() does that). - * + * *

* See related method getRefererInfo(). - * + * * @return a list of ReferSubscriber objects or an empty list if there are none. */ public List getRefererList() { @@ -1735,7 +1826,7 @@ public List getRefererList() { /** * Creates an URI object useful for passing into methods such as SipPhone.refer(). - * + * * @param scheme "sip:" or "sips:" or null if the scheme is already included in the userHostPort * parameter. * @param userHostPort Addressing information in the form of: user@host:port. Port is not @@ -1813,10 +1904,10 @@ public SipURI getUri(String scheme, String userHostPort, String transportUriPara * following: provisional (status / 100 == 1), UNAUTHORIZED, PROXY_AUTHENTICATION_REQUIRED, OK and * ACCEPTED. Any other status code, or a response timeout or any other error, is considered fatal * to the subscription. - * + * *

* This method blocks until one of the above outcomes is reached. - * + * *

* In the case of a positive response status code, this method returns a ReferSubscriber object * that represents the implicit subscription. You can save the returned object yourself or @@ -1825,13 +1916,13 @@ public SipURI getUri(String scheme, String userHostPort, String transportUriPara * REFER-NOTIFY sequence as well as for future SUBSCRIBE-NOTIFY sequences on this subscription and * also to find out details at any given time such as the subscription state, amount of time left * on the subscription, termination reason, details of received responses and requests, etc. - * + * *

* In the case of a positive response status code (a non-null object is returned), you may find * out more about the response that was just received by calling the ReferSubscriber methods * getReturnCode() and getCurrentResponse(). Your next step will then be to call the * ReferSubscriber's processResponse() method to proceed with the REFER processing. - * + * *

* In the case of a fatal outcome, no subscription object is created and null is returned. In this * case, call the usual SipUnit failed-operation methods to find out what happened (ie, call this @@ -1839,7 +1930,7 @@ public SipURI getUri(String scheme, String userHostPort, String transportUriPara * getReturnCode() method will tell you the response status code that was received from the * network (unless it is an internal SipUnit error code, see the SipSession javadoc for more on * that). - * + * * @param refereeUri The URI (ie, sip:bob@nist.gov) of the far end of the subscription. This is * the party the REFER request is sent to. * @param referToUri The URI that the refereeUri is to refer to. You can use SipPhone.getUri() to @@ -1860,7 +1951,7 @@ public SipURI getUri(String scheme, String userHostPort, String transportUriPara * viaNonProxyRoute node which is specified as "hostaddress:port/transport" i.e. * 129.1.22.333:5060/UDP. A route header will be added to the REFER for this, before the * request is sent. - * + * * @return ReferSubscriber object representing the implicit subscription if the operation is * successful so far, null otherwise. */ @@ -1873,10 +1964,10 @@ public ReferSubscriber refer(String refereeUri, SipURI referToUri, String eventI * This method is the same as the basic out-of-dialog refer() method except that it allows the * caller to specify a message body and/or additional JAIN-SIP API message headers to add to or * replace in the outbound message. Use of this method requires knowledge of the JAIN-SIP API. - * + * *

* The extra parameters supported by this method are: - * + * * @param additionalHeaders ArrayList of javax.sip.header.Header, each element a SIP header to add * to the outbound message. These headers are added to the message after a correct message * has been constructed. Note that if you try to add a header that there is only supposed @@ -1943,10 +2034,10 @@ public ReferSubscriber refer(String refereeUri, SipURI referToUri, String eventI * This method is the same as the basic out-of-dialog refer() method except that it allows the * caller to specify a message body and/or additional message headers to add to or replace in the * outbound message without requiring knowledge of the JAIN-SIP API. - * + * *

* The extra parameters supported by this method are: - * + * * @param body A String to be used as the body of the message. Parameters contentType, * contentSubType must both be non-null to get the body included in the message. Use null * for no body bytes. @@ -1972,7 +2063,7 @@ public ReferSubscriber refer(String refereeUri, SipURI referToUri, String eventI * occur if your headers are not syntactically correct or contain nonsensical values (the * message may not pass through the local SIP stack). Use null for no replacement of * message headers. - * + * */ public ReferSubscriber refer(String refereeUri, SipURI referToUri, String eventId, long timeout, String viaNonProxyRoute, String body, String contentType, String contentSubType, @@ -1995,10 +2086,10 @@ public ReferSubscriber refer(String refereeUri, SipURI referToUri, String eventI * provisional (status / 100 == 1), UNAUTHORIZED, PROXY_AUTHENTICATION_REQUIRED, OK and ACCEPTED. * Any other status code, or a response timeout or any other error, is considered fatal to the * subscription. - * + * *

* This method blocks until one of the above outcomes is reached. - * + * *

* In the case of a positive response status code, this method returns a ReferSubscriber object * that represents the implicit subscription. You can save the returned object yourself or @@ -2007,13 +2098,13 @@ public ReferSubscriber refer(String refereeUri, SipURI referToUri, String eventI * REFER-NOTIFY sequence as well as for future SUBSCRIBE-NOTIFY sequences on this subscription and * also to find out details at any given time such as the subscription state, amount of time left * on the subscription, termination reason, details of received responses and requests, etc. - * + * *

* In the case of a positive response status code (a non-null object is returned), you may find * out more about the response that was just received by calling the ReferSubscriber methods * getReturnCode() and getCurrentResponse(). Your next step will then be to call the * ReferSubscriber's processResponse() method to proceed with the REFER processing. - * + * *

* In the case of a fatal outcome, no subscription object is created and null is returned. In this * case, call the usual SipUnit failed-operation methods to find out what happened (ie, call this @@ -2021,7 +2112,7 @@ public ReferSubscriber refer(String refereeUri, SipURI referToUri, String eventI * getReturnCode() method will tell you the response status code that was received from the * network (unless it is an internal SipUnit error code, see the SipSession javadoc for more on * that). - * + * * @param dialog The existing dialog that this REFER should be associated with. You can get it * from SipCall.getDialog() or ReferSubscriber.getDialog(). * @param referToUri The URI that the far end of the dialog is to refer to. You can use @@ -2034,7 +2125,7 @@ public ReferSubscriber refer(String refereeUri, SipURI referToUri, String eventI * refresh() or unsubscribe(). * @param timeout The maximum amount of time to wait for a response, in milliseconds. Use a value * of 0 to wait indefinitely. - * + * * @return ReferSubscriber object representing the implicit subscription if the operation is * successful so far, null otherwise. */ @@ -2046,10 +2137,10 @@ public ReferSubscriber refer(Dialog dialog, SipURI referToUri, String eventId, l * This method is the same as the basic in-dialog refer() method except that it allows the caller * to specify a message body and/or additional JAIN-SIP API message headers to add to or replace * in the outbound message. Use of this method requires knowledge of the JAIN-SIP API. - * + * *

* The extra parameters supported by this method are: - * + * * @param additionalHeaders ArrayList of javax.sip.header.Header, each element a SIP header to add * to the outbound message. These headers are added to the message after a correct message * has been constructed. Note that if you try to add a header that there is only supposed @@ -2114,10 +2205,10 @@ public ReferSubscriber refer(Dialog dialog, SipURI referToUri, String eventId, l * This method is the same as the basic in-dialog refer() method except that it allows the caller * to specify a message body and/or additional message headers to add to or replace in the * outbound message without requiring knowledge of the JAIN-SIP API. - * + * *

* The extra parameters supported by this method are: - * + * * @param body A String to be used as the body of the message. Parameters contentType, * contentSubType must both be non-null to get the body included in the message. Use null * for no body bytes. @@ -2143,7 +2234,7 @@ public ReferSubscriber refer(Dialog dialog, SipURI referToUri, String eventId, l * occur if your headers are not syntactically correct or contain nonsensical values (the * message may not pass through the local SIP stack). Use null for no replacement of * message headers. - * + * */ public ReferSubscriber refer(Dialog dialog, SipURI referToUri, String eventId, long timeout, String body, String contentType, String contentSubType, ArrayList additionalHeaders, diff --git a/src/main/java/org/cafesip/sipunit/SipStack.java b/src/main/java/org/cafesip/sipunit/SipStack.java index 18e39aaa6c..b0da8a45c7 100644 --- a/src/main/java/org/cafesip/sipunit/SipStack.java +++ b/src/main/java/org/cafesip/sipunit/SipStack.java @@ -16,37 +16,20 @@ package org.cafesip.sipunit; +import gov.nist.javax.sip.ResponseEventExt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import gov.nist.javax.sip.ResponseEventExt; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.text.ParseException; -import java.util.Date; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.ListIterator; -import java.util.Properties; -import java.util.Random; - -import javax.sip.DialogTerminatedEvent; -import javax.sip.IOExceptionEvent; -import javax.sip.InvalidArgumentException; -import javax.sip.ListeningPoint; -import javax.sip.RequestEvent; -import javax.sip.ResponseEvent; -import javax.sip.SipFactory; -import javax.sip.SipListener; -import javax.sip.SipProvider; -import javax.sip.TimeoutEvent; -import javax.sip.TransactionTerminatedEvent; +import javax.sip.*; import javax.sip.address.AddressFactory; import javax.sip.header.HeaderFactory; import javax.sip.header.RecordRouteHeader; import javax.sip.header.RouteHeader; import javax.sip.message.MessageFactory; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.text.ParseException; +import java.util.*; /** * This class is the starting point for a SipUnit test. Before establishing any SIP sessions, the @@ -212,6 +195,41 @@ public SipStack(String proto, int port) throws Exception { this(proto, port, null); } + /** + * This method is used to create a SipPhone object. The SipPhone class simulates a SIP User Agent. + * The SipPhone object is used to communicate with other SIP agents. Using a SipPhone object, the + * test program can make one (or more, in future) outgoing calls or (and, in future) receive one + * (or more, in future) incoming calls. + *

+ * This factory method provides the option to configure a different address for the registrar and the proxy component + * in SIP backend. + * + * @param registrarHost host name or address of the SIP registrar. The registrar handles registrations in a SIP backend, + * and provides credentials for a registering client. + * If a host name is given here, it must resolve to a valid, reachable DNS address. + * @param registrarPort port number into with the registrar server listens to for SIP messages and + * connections. + * @param proxyHost host name or address of the SIP proxy to use. The proxy is used for outbound calling on + * a per-call basis. If a host name is given here, it must resolve to a valid, reachable DNS address. + * @param proxyProto used to specify the protocol for communicating with the proxy server - "udp" + * or "tcp". + * @param proxyPort port number into with the proxy server listens to for SIP messages and + * connections. + * @param me "Address of Record" URI of the phone user. Each SipPhone is associated with one user. + * This parameter is used in the "from" header field. + * @return A new SipPhone object. + * @throws InvalidArgumentException + * @throws ParseException + */ + public SipPhone createSipPhone(String registrarHost, int registrarPort, String proxyHost, String proxyProto, int proxyPort, String me) throws InvalidArgumentException, ParseException { + try { + return new SipPhone(this, registrarHost, registrarPort, proxyHost, proxyProto, proxyPort, me); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + } + + /** * This method is used to create a SipPhone object. The SipPhone class simulates a SIP User Agent. * The SipPhone object is used to communicate with other SIP agents. Using a SipPhone object, the diff --git a/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java new file mode 100644 index 0000000000..a5864fe817 --- /dev/null +++ b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java @@ -0,0 +1,244 @@ +package org.cafesip.sipunit.test.misc; + +import org.cafesip.sipunit.HeaderConfiguration; +import org.cafesip.sipunit.SipPhone; +import org.cafesip.sipunit.SipStack; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sip.RequestEvent; +import javax.sip.address.Address; +import javax.sip.header.*; +import javax.sip.message.Request; +import javax.sip.message.Response; +import java.text.ParseException; +import java.util.Arrays; +import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import static org.cafesip.sipunit.SipAssert.assertLastOperationSuccess; +import static org.cafesip.sipunit.SipAssert.awaitStackDispose; +import static org.junit.Assert.*; + +/** + * Tests the integration of SipUnit clients in a case where the registrar is separate from the proxy. Also tests out + * header override methods which are used in validation of registrar error-handling capabilities. + *

+ * Created by TELES AG on 08/01/2018. + */ +public class TestExternalRegistrar { + private static final Logger LOG = LoggerFactory.getLogger(TestExternalRegistrar.class); + + private static final String LOCALHOST = "127.0.0.1"; + + private SipStack sipStack; + private SipPhone ua; + private final int uaPort = 5081; + private final String uaProtocol = "UDP"; + private final String uaContact = "sip:client@127.0.0.1:5081"; + + + private SipStack sipStackRegistrar; + private SipPhone registrar; + private final int registrarPort = 5080; + private final String registrarProtocol = "UDP"; + private final String registrarContact = "sip:registrar@127.0.0.1:5080"; + + private final int proxyPort = 5090; + private final String proxyProtocol = "UDP"; + + private Properties uaProperties = new Properties(); + private final Properties registrarProperties = new Properties(); + + private ExecutorService executorService = Executors.newCachedThreadPool(); + + @Before + public void setup() throws Exception { + uaProperties.setProperty("javax.sip.IP_ADDRESS", LOCALHOST); + uaProperties.setProperty("javax.sip.STACK_NAME", "testAgent"); + uaProperties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "16"); + uaProperties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "testAgent_debug.txt"); + uaProperties.setProperty("gov.nist.javax.sip.SERVER_LOG", "testAgent_log.txt"); + uaProperties.setProperty("gov.nist.javax.sip.READ_TIMEOUT", "1000"); + uaProperties.setProperty("gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS", "false"); + uaProperties.setProperty("gov.nist.javax.sip.PASS_INVITE_NON_2XX_ACK_TO_LISTENER", "true"); + + sipStack = new SipStack(uaProtocol, uaPort, uaProperties); + ua = sipStack.createSipPhone(LOCALHOST, registrarPort, LOCALHOST, proxyProtocol, proxyPort, uaContact); + + registrarProperties.setProperty("javax.sip.STACK_NAME", "testRegistrar"); + registrarProperties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "16"); + registrarProperties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "testRegistrar_debug.txt"); + registrarProperties.setProperty("gov.nist.javax.sip.SERVER_LOG", "testRegistrar_log.txt"); + registrarProperties.setProperty("gov.nist.javax.sip.READ_TIMEOUT", "1000"); + registrarProperties.setProperty("gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS", "false"); + registrarProperties.setProperty("gov.nist.javax.sip.PASS_INVITE_NON_2XX_ACK_TO_LISTENER", "true"); + registrarProperties.setProperty("javax.sip.IP_ADDRESS", LOCALHOST); + + sipStackRegistrar = new SipStack(registrarProtocol, registrarPort, registrarProperties); + registrar = sipStackRegistrar.createSipPhone(registrarContact); + } + + + /** + * Release the sipStack and a user agent for the test. + */ + @After + public void tearDown() { + ua.unregister(uaContact, 1000); + ua.dispose(); + awaitStackDispose(sipStack); + + registrar.dispose(); + awaitStackDispose(sipStackRegistrar); + } + + @Test + public void testRegisterWithRegistrar() throws Exception { + registrar.setSupportRegisterRequests(true); + assertTrue(registrar.listenRequestMessage()); + + Future registrarResult = executorService.submit(new Runnable() { + @Override + public void run() { + RequestEvent requestEvent = registrar.waitRequest(10000); + assertNotNull(requestEvent); + Response response = null; + try { + response = registrar.getParent().getMessageFactory().createResponse(200, requestEvent.getRequest()); + } catch (ParseException e) { + e.printStackTrace(); + } + registrar.sendReply(requestEvent, response); + } + }); + + assertTrue(ua.register("amit", "a1b2c3d4", uaContact, 4890, 1000000)); + assertLastOperationSuccess("user a registration - " + ua.format(), ua); + + registrarResult.get(); + + assertNotNull(ua.getLastRegistrationRequest()); + assertNotNull(ua.getLastRegistrationResponse()); + } + + @Test + public void testRegisterWithCallIdOverride() throws Exception { + HeaderConfiguration headerConfiguration = new HeaderConfiguration(); + + final CallIdHeader overrideHeader = ua.getParent().getHeaderFactory().createCallIdHeader("test-call-id"); + headerConfiguration.setCallIdHeader(overrideHeader); + + testHeaderOverride(headerConfiguration, overrideHeader); + } + + @Test + public void testRegisterWithToOverride() throws Exception { + HeaderConfiguration headerConfiguration = new HeaderConfiguration(); + + final Address expectedAddress = ua.getParent().getAddressFactory().createAddress("sip:testaddr@test.com"); + final ToHeader overrideHeader = ua.getParent().getHeaderFactory().createToHeader(expectedAddress, "test-tag"); + headerConfiguration.setToHeader(overrideHeader); + + testHeaderOverride(headerConfiguration, overrideHeader); + } + + @Test + public void testRegisterWithFromOverride() throws Exception { + final HeaderConfiguration headerConfiguration = new HeaderConfiguration(); + + final Address expectedAddress = ua.getParent().getAddressFactory().createAddress("sip:testaddr@test.com"); + final FromHeader overrideHeader = ua.getParent().getHeaderFactory().createFromHeader(expectedAddress, "test-tag"); + headerConfiguration.setFromHeader(overrideHeader); + + testHeaderOverride(headerConfiguration, overrideHeader); + } + + @Test + public void testRegisterWithCSeqOverride() throws Exception { + HeaderConfiguration headerConfiguration = new HeaderConfiguration(); + + final CSeqHeader overrideHeader = ua.getParent().getHeaderFactory().createCSeqHeader(555l, Request.REGISTER); + headerConfiguration.setCSeqHeader(overrideHeader); + + testHeaderOverride(headerConfiguration, overrideHeader); + } + + @Test + public void testRegisterWithContactOverride() throws Exception { + HeaderConfiguration headerConfiguration = new HeaderConfiguration(); + + final Address expectedAddress = ua.getParent().getAddressFactory().createAddress("sip:testaddr@test.com"); + final ContactHeader overrideHeader = ua.getParent().getHeaderFactory().createContactHeader(expectedAddress); + headerConfiguration.setContactHeader(overrideHeader); + + testHeaderOverride(headerConfiguration, overrideHeader); + } + + @Test + public void testRegisterWithExpiresOverride() throws Exception { + HeaderConfiguration headerConfiguration = new HeaderConfiguration(); + + final ExpiresHeader overrideHeader = ua.getParent().getHeaderFactory().createExpiresHeader(3600); + headerConfiguration.setExpiresHeader(overrideHeader); + + testHeaderOverride(headerConfiguration, overrideHeader); + } + + @Test + public void testRegisterUserAgentOverride() throws Exception { + HeaderConfiguration headerConfiguration = new HeaderConfiguration(); + + final UserAgentHeader overrideHeader = ua.getParent().getHeaderFactory() + .createUserAgentHeader(Arrays.asList("test-agent")); + headerConfiguration.setUserAgentHeader(overrideHeader); + + testHeaderOverride(headerConfiguration, overrideHeader); + } + + @Test + public void testRegisterWithOmittedContactOverride() throws Exception { + HeaderConfiguration headerConfiguration = new HeaderConfiguration(); + headerConfiguration.setIgnoreContact(true); + + testHeaderOverride(headerConfiguration, ContactHeader.NAME, null); + } + + private void testHeaderOverride(final HeaderConfiguration headerConfiguration, final Header overrideHeader) throws InterruptedException, java.util.concurrent.ExecutionException { + testHeaderOverride(headerConfiguration, overrideHeader.getName(), overrideHeader); + } + + private void testHeaderOverride(final HeaderConfiguration headerConfiguration, final String headerName, final Header expectedHeaderValue) throws InterruptedException, java.util.concurrent.ExecutionException { + registrar.setSupportRegisterRequests(true); + assertTrue(registrar.listenRequestMessage()); + + Future registrarResult = executorService.submit(new Runnable() { + @Override + public void run() { + RequestEvent requestEvent = registrar.waitRequest(10000); + assertNotNull(requestEvent); + + Header receivedHeader = requestEvent.getRequest().getHeader(headerName); + assertEquals(expectedHeaderValue, receivedHeader); + + Response response = null; + try { + response = registrar.getParent().getMessageFactory().createResponse(200, requestEvent.getRequest()); + } catch (ParseException e) { + e.printStackTrace(); + } + registrar.sendReply(requestEvent, response); + } + }); + + assertTrue(ua.register("amit", "a1b2c3d4", uaContact, 4890, 1000000, headerConfiguration)); + assertLastOperationSuccess("user a registration - " + ua.format(), ua); + + registrarResult.get(); + } +}