From 0c2730385eecdda9034e3fa67d3f150c3783542b Mon Sep 17 00:00:00 2001 From: Mario Milas Date: Fri, 25 Aug 2017 08:23:38 +0200 Subject: [PATCH 1/7] Add external registrar support and header override The current implementation did not predict the possibility of an external registrar component (separate from proxy). This commit adds the modification to the SipPhone and SipStack in order to create phones aimed at registering on this external registrar. Also added the possibility of header overrides within the REGISTER request in order to stress-test the registrar processing. --- .../cafesip/sipunit/HeaderConfiguration.java | 93 +++ .../java/org/cafesip/sipunit/SipPhone.java | 619 ++++++++++-------- .../java/org/cafesip/sipunit/SipStack.java | 30 + .../test/misc/TestExternalRegistrar.java | 217 ++++++ 4 files changed, 691 insertions(+), 268 deletions(-) create mode 100644 src/main/java/org/cafesip/sipunit/HeaderConfiguration.java create mode 100644 src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java 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..e69326a59f --- /dev/null +++ b/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java @@ -0,0 +1,93 @@ +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; + + 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; + } +} diff --git a/src/main/java/org/cafesip/sipunit/SipPhone.java b/src/main/java/org/cafesip/sipunit/SipPhone.java index ed834a085f..9c717ba2fb 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,16 @@ 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.registrarHost = registrarHost; + this.registrarPort = registrarPort; } /** @@ -146,11 +137,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 +158,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 +196,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 +245,192 @@ 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.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()); + } + + + // 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 +438,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 +511,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 @@ -476,7 +550,7 @@ private Response sendRegistrationMessage(Request msg, String user, String passwo status_code = response.getStatusCode(); 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); @@ -520,7 +594,7 @@ private Response sendRegistrationMessage(Request msg, String user, String passwo } 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 +608,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 +624,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 +647,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 +662,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 +673,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 +710,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 +736,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 +787,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 +809,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 +829,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 +851,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 +880,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 +901,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 +998,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 +1011,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 +1032,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 +1043,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 +1083,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 +1111,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 +1132,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 +1180,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 +1189,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 +1210,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 +1219,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 +1266,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 +1275,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 +1284,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 +1410,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 +1431,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 +1446,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 +1508,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 +1522,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 +1539,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 +1558,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 +1566,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 +1579,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 +1594,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 +1656,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 +1675,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 +1701,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 +1718,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 +1755,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 +1780,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 +1806,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 +1818,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 +1896,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 +1908,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 +1922,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 +1943,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 +1956,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 +2026,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 +2055,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 +2078,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 +2090,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 +2104,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 +2117,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 +2129,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 +2197,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 +2226,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..0231598eae 100644 --- a/src/main/java/org/cafesip/sipunit/SipStack.java +++ b/src/main/java/org/cafesip/sipunit/SipStack.java @@ -212,6 +212,36 @@ 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. + * + * @param proxyHost host name or address of the SIP proxy to use. The proxy is used for + * registering and outbound calling on a per-call basis. If this parameter is a null value, + * any registration requests will be sent to the "host" part of the "me" parameter (see + * below) and any attempt to make an outbound call via proxy will fail. 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..f287ac8e28 --- /dev/null +++ b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java @@ -0,0 +1,217 @@ +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.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(); + } + + @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); + } + + private void testHeaderOverride(final HeaderConfiguration headerConfiguration, final Header overrideHeader) 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(overrideHeader.getName()); + assertEquals(overrideHeader,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(); + } +} From b2f4e77013a0f2997f690b20241684294c493854 Mon Sep 17 00:00:00 2001 From: Mario Milas Date: Tue, 9 Jan 2018 08:48:12 +0100 Subject: [PATCH 2/7] Update register method Javadoc --- .../java/org/cafesip/sipunit/SipStack.java | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/cafesip/sipunit/SipStack.java b/src/main/java/org/cafesip/sipunit/SipStack.java index 0231598eae..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 @@ -217,12 +200,17 @@ public SipStack(String proto, int port) throws Exception { * 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 proxyHost host name or address of the SIP proxy to use. The proxy is used for - * registering and outbound calling on a per-call basis. If this parameter is a null value, - * any registration requests will be sent to the "host" part of the "me" parameter (see - * below) and any attempt to make an outbound call via proxy will fail. If a host name is - * given here, it must resolve to a valid, reachable DNS address. + * @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 From 0d3bd4be5ea0b3fb97845a3f2f1ce784ec385e22 Mon Sep 17 00:00:00 2001 From: Mario Milas Date: Fri, 25 Aug 2017 12:10:20 +0200 Subject: [PATCH 3/7] Fix NOTIFY registration when creating a registrar-bound phone --- src/main/java/org/cafesip/sipunit/SipPhone.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/cafesip/sipunit/SipPhone.java b/src/main/java/org/cafesip/sipunit/SipPhone.java index 9c717ba2fb..d16775f51f 100644 --- a/src/main/java/org/cafesip/sipunit/SipPhone.java +++ b/src/main/java/org/cafesip/sipunit/SipPhone.java @@ -127,6 +127,7 @@ protected SipPhone(SipStack stack, String host, String me) 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; From 823f20ea9569ebcdbe53b6694c387414a97352de Mon Sep 17 00:00:00 2001 From: Mario Milas Date: Mon, 28 Aug 2017 08:23:16 +0200 Subject: [PATCH 4/7] Fix an issue with registration response not being stored --- src/main/java/org/cafesip/sipunit/SipPhone.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/cafesip/sipunit/SipPhone.java b/src/main/java/org/cafesip/sipunit/SipPhone.java index d16775f51f..a74b93a34d 100644 --- a/src/main/java/org/cafesip/sipunit/SipPhone.java +++ b/src/main/java/org/cafesip/sipunit/SipPhone.java @@ -531,6 +531,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) { @@ -549,6 +550,7 @@ 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)) { @@ -591,6 +593,7 @@ 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); From 0ec00526e7673bd6675ad7836ade5ab2571aa407 Mon Sep 17 00:00:00 2001 From: rotr Date: Thu, 31 Aug 2017 11:06:44 +0200 Subject: [PATCH 5/7] Add parameter in header config to omit Contact for some REGISTER tests --- .../cafesip/sipunit/HeaderConfiguration.java | 13 +++++++++++++ .../java/org/cafesip/sipunit/SipPhone.java | 13 +++++++------ .../test/misc/TestExternalRegistrar.java | 18 +++++++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java b/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java index e69326a59f..accaa6dd40 100644 --- a/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java +++ b/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java @@ -27,6 +27,11 @@ public class HeaderConfiguration { private ExpiresHeader expiresHeader; + /** + * 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; } @@ -90,4 +95,12 @@ public ExpiresHeader getExpiresHeader() { public void setExpiresHeader(ExpiresHeader expiresHeader) { this.expiresHeader = expiresHeader; } + + 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 a74b93a34d..45a82df0b8 100644 --- a/src/main/java/org/cafesip/sipunit/SipPhone.java +++ b/src/main/java/org/cafesip/sipunit/SipPhone.java @@ -335,14 +335,15 @@ public boolean register(SipURI requestUri, String user, String password, String headerConfiguration.getCSeqHeader(), headerConfiguration.getFromHeader(), headerConfiguration.getToHeader(), headerConfiguration.getViaHeaders(), headerConfiguration.getMaxForwardsHeader()); - if (headerConfiguration.getContactHeader() == null) { - msg.addHeader(contactInfo.getContactHeader()); // use - // setHeader()? - } else { - msg.addHeader(headerConfiguration.getContactHeader()); + 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); diff --git a/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java index f287ac8e28..80909fab19 100644 --- a/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java +++ b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java @@ -27,7 +27,7 @@ /** * 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 { @@ -186,7 +186,19 @@ public void testRegisterWithExpiresOverride() throws Exception { 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()); @@ -196,8 +208,8 @@ public void run() { RequestEvent requestEvent = registrar.waitRequest(10000); assertNotNull(requestEvent); - Header receivedHeader = requestEvent.getRequest().getHeader(overrideHeader.getName()); - assertEquals(overrideHeader,receivedHeader); + Header receivedHeader = requestEvent.getRequest().getHeader(headerName); + assertEquals(expectedHeaderValue, receivedHeader); Response response = null; try { From 9bf20fa6cce71e0a9c0d91fd15abb8851e1204a5 Mon Sep 17 00:00:00 2001 From: Mario Milas Date: Mon, 28 Aug 2017 10:28:14 +0200 Subject: [PATCH 6/7] Add user agent to header configuration --- .../org/cafesip/sipunit/HeaderConfiguration.java | 10 ++++++++++ src/main/java/org/cafesip/sipunit/SipPhone.java | 3 +++ .../sipunit/test/misc/TestExternalRegistrar.java | 12 ++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java b/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java index accaa6dd40..be405bdb1a 100644 --- a/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java +++ b/src/main/java/org/cafesip/sipunit/HeaderConfiguration.java @@ -27,6 +27,8 @@ public class HeaderConfiguration { private ExpiresHeader expiresHeader; + private UserAgentHeader userAgentHeader; + /** * If set to true, will not add the {@link ContactHeader} to the REGISTER request in {@link SipPhone} */ @@ -96,6 +98,14 @@ 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; } diff --git a/src/main/java/org/cafesip/sipunit/SipPhone.java b/src/main/java/org/cafesip/sipunit/SipPhone.java index 45a82df0b8..46cb570f4f 100644 --- a/src/main/java/org/cafesip/sipunit/SipPhone.java +++ b/src/main/java/org/cafesip/sipunit/SipPhone.java @@ -353,6 +353,9 @@ public boolean register(SipURI requestUri, String user, String password, String 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 diff --git a/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java index 80909fab19..cbf655d276 100644 --- a/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java +++ b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java @@ -15,6 +15,7 @@ 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; @@ -186,6 +187,17 @@ public void testRegisterWithExpiresOverride() throws Exception { 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(); From 7f510aac6bce6bde00c2d530b56cbc866045ca22 Mon Sep 17 00:00:00 2001 From: Mario Milas Date: Tue, 9 Jan 2018 13:16:07 +0100 Subject: [PATCH 7/7] Add registration request and response assertion for ext. registrar test --- .../org/cafesip/sipunit/test/misc/TestExternalRegistrar.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java index cbf655d276..a5864fe817 100644 --- a/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java +++ b/src/test/java/org/cafesip/sipunit/test/misc/TestExternalRegistrar.java @@ -122,6 +122,9 @@ public void run() { assertLastOperationSuccess("user a registration - " + ua.format(), ua); registrarResult.get(); + + assertNotNull(ua.getLastRegistrationRequest()); + assertNotNull(ua.getLastRegistrationResponse()); } @Test