summaryrefslogtreecommitdiff
path: root/pjsip-apps
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2008-07-07 20:14:41 +0000
committerBenny Prijono <bennylp@teluu.com>2008-07-07 20:14:41 +0000
commit28ec1de558e5b6fa5d6695ee797dc2774fb8739f (patch)
tree6a1603be3a43e159dd0829623e719c0aed7b9be0 /pjsip-apps
parenta7c65d1eaeec40ce135ba5b1c1d14a0255bb99f2 (diff)
Added mod_recvfrom.py testing module and some registrar test scenarios
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2110 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip-apps')
-rw-r--r--pjsip-apps/src/test-pjsua/inc_sip.py112
-rw-r--r--pjsip-apps/src/test-pjsua/mod_recvfrom.py92
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/200_reg_good_enocredentiall.py15
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/201_reg_good_ok.py23
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/202_reg_good_ok_wildcard.py23
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/205_reg_good_no_realm.py16
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/206_reg_good_efailedcredential.py26
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/208_reg_good_retry_nonce_ok.py29
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/215_reg_good_multi_ok.py28
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/220_reg_good_ims_ok.py26
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/230_reg_bad_fail_stale_true.py41
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/231_reg_bad_fail_stale_false_nonce_changed.py41
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-recvfrom/234_reg_bad_stale_ok.py41
13 files changed, 503 insertions, 10 deletions
diff --git a/pjsip-apps/src/test-pjsua/inc_sip.py b/pjsip-apps/src/test-pjsua/inc_sip.py
index c8dadc69..6d5ef052 100644
--- a/pjsip-apps/src/test-pjsua/inc_sip.py
+++ b/pjsip-apps/src/test-pjsua/inc_sip.py
@@ -49,6 +49,13 @@ def get_tag(msg, hdr="To"):
return ""
#return re.split("[;& ]", s)
+def get_header(msg, hname):
+ headers = msg.splitlines()
+ for hdr in headers:
+ hfields = hdr.split(": ", 2)
+ if hfields[0]==hname:
+ return hfields[1]
+ return None
class Dialog:
sock = None
@@ -65,7 +72,7 @@ class Dialog:
inv_branch = ""
trace_enabled = True
last_request = ""
- def __init__(self, dst_addr, dst_port=5060, tcp=False, trace=True):
+ def __init__(self, dst_addr, dst_port=5060, tcp=False, trace=True, local_port=0):
self.dst_addr = dst_addr
self.dst_port = dst_port
self.tcp = tcp
@@ -75,7 +82,7 @@ class Dialog:
self.sock.connect(dst_addr, dst_port)
else:
self.sock = socket(AF_INET, SOCK_DGRAM)
- self.sock.bind(("127.0.0.1", 0))
+ self.sock.bind(("127.0.0.1", local_port))
self.local_ip, self.local_port = self.sock.getsockname()
self.trace("Dialog socket bound to " + self.local_ip + ":" + str(self.local_port))
@@ -113,6 +120,19 @@ class Dialog:
msg = msg + sdp
return msg
+ def create_response(self, request, code, reason, to_tag=""):
+ response = "SIP/2.0 " + str(code) + " " + reason + "\r\n"
+ lines = request.splitlines()
+ for line in lines:
+ hdr = line.split(":", 1)[0]
+ if hdr in ["Via", "From", "To", "CSeq", "Call-ID"]:
+ if hdr=="To" and to_tag!="":
+ line = line + ";tag=" + to_tag
+ elif hdr=="Via":
+ line = line + ";received=127.0.0.1"
+ response = response + line + "\r\n"
+ return response
+
def create_invite(self, sdp, extra_headers=""):
self.inv_branch = str(random.random())
return self.create_req("INVITE", sdp, branch=self.inv_branch, extra_headers=extra_headers)
@@ -123,15 +143,18 @@ class Dialog:
def create_bye(self, extra_headers=""):
return self.create_req("BYE", "", extra_headers)
- def send_msg(self, msg):
+ def send_msg(self, msg, dst_addr=None):
if (is_request(msg)):
self.last_request = msg.split(" ", 1)[0]
- self.trace("============== TX MSG ============= \n" + msg)
- self.sock.sendto(msg, 0, (self.dst_addr, self.dst_port))
+ if not dst_addr:
+ dst_addr = (self.dst_addr, self.dst_port)
+ self.trace("============== TX MSG to " + str(dst_addr) + " ============= \n" + msg)
+ self.sock.sendto(msg, 0, dst_addr)
- def wait_msg(self, timeout):
+ def wait_msg_from(self, timeout):
endtime = time.time() + timeout
msg = ""
+ src_addr = None
while time.time() < endtime:
readset = select([self.sock], [], [], timeout)
if len(readset) < 1 or not self.sock in readset[0]:
@@ -143,22 +166,25 @@ class Dialog:
print "select other error"
continue
try:
- msg = self.sock.recv(2048)
+ msg, src_addr = self.sock.recvfrom(2048)
except:
print "recv() exception: ", sys.exc_info()[0]
continue
if msg=="":
- return ""
+ return "", None
if self.last_request=="INVITE" and self.rem_tag=="":
self.rem_tag = get_tag(msg, "To")
self.rem_tag = self.rem_tag.rstrip("\r\n;")
if self.rem_tag != "":
self.rem_tag = ";tag=" + self.rem_tag
self.trace("=== rem_tag:" + self.rem_tag)
- self.trace("=========== RX MSG ===========\n" + msg)
- return msg
+ self.trace("=========== RX MSG from " + str(src_addr) + " ===========\n" + msg)
+ return (msg, src_addr)
+ def wait_msg(self, timeout):
+ return self.wait_msg_from(timeout)[0]
+
# Send request and wait for final response
def send_request_wait(self, msg, timeout):
t1 = 1.0
@@ -236,3 +262,69 @@ class SendtoCfg:
self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
self.inst_param.enable_buffer = enable_buffer
+
+class RecvfromTransaction:
+ # The test title for this transaction
+ title = ""
+ # Optinal list of pjsua command and optional expect patterns
+ # to be invoked to make pjsua send a request
+ # Sample:
+ # (to make call and wait for INVITE to be sent)
+ # cmds = [ ["m"], ["sip:127.0.0.1", "INVITE sip:"] ]
+ cmds = []
+ # Check if the CSeq must be greater than last Cseq?
+ check_cseq = True
+ # List of RE patterns that must exists in incoming request
+ include = []
+ # List of RE patterns that MUST NOT exist in incoming request
+ exclude = []
+ # Response code to send
+ resp_code = 0
+ # Additional list of headers to be sent on the response
+ # Note: no need to add CRLF on the header
+ resp_hdr = []
+ # Message body. This should include the Content-Type header too.
+ # Sample:
+ # body = """Content-Type: application/sdp\r\n
+ # \r\n
+ # v=0\r\n
+ # ...
+ # """
+ body = None
+ # Pattern to be expected on pjsua when receiving the response
+ expect = ""
+
+ def __init__(self, title, resp_code, check_cseq=True,
+ include=[], exclude=[], cmds=[], resp_hdr=[], resp_body=None, expect=""):
+ self.title = title
+ self.cmds = cmds
+ self.include = include
+ self.exclude = exclude
+ self.resp_code = resp_code
+ self.resp_hdr = resp_hdr
+ self.resp_body = resp_body
+ self.expect = expect
+
+
+class RecvfromCfg:
+ # Test name
+ name = ""
+ # pjsua InstanceParam
+ inst_param = None
+ # List of RecvfromTransaction
+ transaction = None
+ # Use TCP?
+ tcp = False
+
+ # Note:
+ # Any "$PORT" string in the pjsua_args will be replaced
+ # by server port
+ def __init__(self, name, pjsua_args, transaction, tcp=False):
+ self.name = name
+ self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
+ self.transaction = transaction
+ self.tcp=tcp
+
+
+
+
diff --git a/pjsip-apps/src/test-pjsua/mod_recvfrom.py b/pjsip-apps/src/test-pjsua/mod_recvfrom.py
new file mode 100644
index 00000000..259083f5
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/mod_recvfrom.py
@@ -0,0 +1,92 @@
+# $Id:$
+import imp
+import sys
+import inc_sip as sip
+import inc_const as const
+import re
+from inc_cfg import *
+
+# Read configuration
+cfg_file = imp.load_source("cfg_file", ARGS[1])
+
+# Default server port (should we randomize?)
+srv_port = 50070
+
+def test_func(test):
+ pjsua = test.process[0]
+ dlg = sip.Dialog("127.0.0.1", pjsua.inst_param.sip_port,
+ local_port=srv_port,
+ tcp=cfg_file.recvfrom_cfg.tcp)
+
+ last_cseq = 0
+ last_method = ""
+ for t in cfg_file.recvfrom_cfg.transaction:
+ # Print transaction title
+ if t.title != "":
+ dlg.trace(t.title)
+ # Run command and expect patterns
+ for c in t.cmds:
+ if c[0] and c[0] != "":
+ pjsua.send(c[0])
+ if len(c)>1 and c[1] and c[1] != "":
+ pjsua.expect(c[1])
+ # Wait for request
+ if t.check_cseq:
+ # Absorbs retransmissions
+ cseq = 0
+ method = last_method
+ while cseq <= last_cseq and method == last_method:
+ request, src_addr = dlg.wait_msg_from(10)
+ if request==None or request=="":
+ raise TestError("Timeout waiting for request")
+ method = request.split(" ", 1)[0]
+ cseq_hval = sip.get_header(request, "CSeq")
+ cseq_hval = cseq_hval.split(" ")[0]
+ cseq = int(cseq_hval)
+ last_cseq = cseq
+ last_method = method
+ else:
+ request, src_addr = dlg.wait_msg_from(10)
+ if request==None or request=="":
+ raise TestError("Timeout waiting for request")
+
+ # Check for include patterns
+ for pat in t.include:
+ if re.search(pat, request, re.M | re.I)==None:
+ if t.title:
+ tname = " in " + t.title + " transaction"
+ else:
+ tname = ""
+ raise TestError("Pattern " + pat + " not found" + tname)
+ # Check for exclude patterns
+ for pat in t.exclude:
+ if re.search(pat, request, re.M | re.I)!=None:
+ if t.title:
+ tname = " in " + t.title + " transaction"
+ else:
+ tname = ""
+ raise TestError("Excluded pattern " + pat + " found" + tname)
+ # Create response
+ response = dlg.create_response(request, t.resp_code, "Status reason")
+ # Add headers to response
+ for h in t.resp_hdr:
+ response = response + h + "\r\n"
+ # Add message body if required
+ if t.body:
+ response = response + t.body
+ # Send response
+ dlg.send_msg(response, src_addr)
+ # Expect something to happen in pjsua
+ if t.expect != "":
+ pjsua.expect(t.expect)
+ # Sync
+ pjsua.sync_stdout()
+
+# Replace "$PORT" with server port in pjsua args
+cfg_file.recvfrom_cfg.inst_param.arg = cfg_file.recvfrom_cfg.inst_param.arg.replace("$PORT", str(srv_port))
+
+# Here where it all comes together
+test = TestParam(cfg_file.recvfrom_cfg.name,
+ [cfg_file.recvfrom_cfg.inst_param],
+ test_func)
+
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/200_reg_good_enocredentiall.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/200_reg_good_enocredentiall.py
new file mode 100644
index 00000000..9a0cd41a
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/200_reg_good_enocredentiall.py
@@ -0,0 +1,15 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT"
+
+req1 = sip.RecvfromTransaction("", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+ expect="PJSIP_ENOCREDENTIAL"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Failed registration test",
+ pjsua, [req1])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/201_reg_good_ok.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/201_reg_good_ok.py
new file mode 100644
index 00000000..5532d73f
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/201_reg_good_ok.py
@@ -0,0 +1,23 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--username user --realm python --password passwd --auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial registration", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+ expect="SIP/2.0 401"
+ )
+
+req2 = sip.RecvfromTransaction("Registration retry with auth", 200,
+ include=["REGISTER sip", "Authorization:",
+ "realm=\"python\"", "username=\"user\"",
+ "nonce=\"1234\"", "response="],
+ expect="registration success"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Successful registration test",
+ pjsua, [req1, req2])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/202_reg_good_ok_wildcard.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/202_reg_good_ok_wildcard.py
new file mode 100644
index 00000000..4fd9cdb7
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/202_reg_good_ok_wildcard.py
@@ -0,0 +1,23 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--username user --realm \"*\" --password passwd --auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial registration", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+ expect="SIP/2.0 401"
+ )
+
+req2 = sip.RecvfromTransaction("Registration retry with auth", 200,
+ include=["REGISTER sip", "Authorization:",
+ "realm=\"python\"", "username=\"user\"",
+ "nonce=\"1234\"", "response="],
+ expect="registration success"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Successful registration with wildcard realm test",
+ pjsua, [req1, req2])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/205_reg_good_no_realm.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/205_reg_good_no_realm.py
new file mode 100644
index 00000000..69c0ecfd
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/205_reg_good_no_realm.py
@@ -0,0 +1,16 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--realm=provider --user=username --password=password"
+
+req1 = sip.RecvfromTransaction("", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+ expect="PJSIP_ENOCREDENTIAL"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Failed registration because of realm test",
+ pjsua, [req1])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/206_reg_good_efailedcredential.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/206_reg_good_efailedcredential.py
new file mode 100644
index 00000000..11f11bfc
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/206_reg_good_efailedcredential.py
@@ -0,0 +1,26 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# Authentication failure test with same nonce
+
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--realm=python --user=username --password=password"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+ )
+
+req2 = sip.RecvfromTransaction("REGISTER retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"1\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""],
+ expect="PJSIP_EFAILEDCREDENTIAL"
+ )
+
+
+recvfrom_cfg = sip.RecvfromCfg("Authentication failure with same nonce",
+ pjsua, [req1, req2])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/208_reg_good_retry_nonce_ok.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/208_reg_good_retry_nonce_ok.py
new file mode 100644
index 00000000..5f47a7bd
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/208_reg_good_retry_nonce_ok.py
@@ -0,0 +1,29 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--realm=python --user=username --password=password " + \
+ "--auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+ )
+
+req2 = sip.RecvfromTransaction("REGISTER first retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"1\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"2\", stale=true"]
+ )
+
+
+req3 = sip.RecvfromTransaction("REGISTER retry with new nonce", 200,
+ include=["REGISTER sip", "Authorization", "nonce=\"2\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ expect="registration success"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Authentication okay after retry with new nonce",
+ pjsua, [req1, req2, req3])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/215_reg_good_multi_ok.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/215_reg_good_multi_ok.py
new file mode 100644
index 00000000..32970f41
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/215_reg_good_multi_ok.py
@@ -0,0 +1,28 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--username theuser1 --realm python1 --password passwd --next-cred " + \
+ "--username theuser2 --realm python2 --password passwd " + \
+ "--auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial registration", 401,
+ include=["REGISTER sip"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python1\", nonce=\"1234\"",
+ "WWW-Authenticate: Digest realm=\"python2\", nonce=\"6789\""],
+ expect="SIP/2.0 401"
+ )
+
+req2 = sip.RecvfromTransaction("Registration retry with auth", 200,
+ include=["REGISTER sip",
+ "Authorization:[\\s\\S]+Authorization:", # Must have 2 Auth hdrs
+ "realm=\"python1\"", "realm=\"python2\"",
+ "username=\"theuser1\"", "username=\"theuser2\"",
+ "nonce=\"1234\"", "nonce=\"6789\"",
+ "response="],
+ expect="registration success"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Multiple authentication challenges",
+ pjsua, [req1, req2])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/220_reg_good_ims_ok.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/220_reg_good_ims_ok.py
new file mode 100644
index 00000000..3d02a1d6
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/220_reg_good_ims_ok.py
@@ -0,0 +1,26 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--username user@ims-domain --realm python --password passwd --use-ims --auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial registration", 401,
+ include=["REGISTER sip", "Authorization",
+ "username=\"user@ims-domain\"",
+ "realm=\"python\""],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1234\""],
+ expect="SIP/2.0 401"
+ )
+
+req2 = sip.RecvfromTransaction("Registration retry with auth", 200,
+ include=["REGISTER sip", "Authorization:",
+ "realm=\"python\"", "username=\"user@ims-domain\"",
+ "nonce=\"1234\"", "response="],
+ # Must not have double Authorization header:
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ expect="registration success"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Successful IMS registration test",
+ pjsua, [req1, req2])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/230_reg_bad_fail_stale_true.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/230_reg_bad_fail_stale_true.py
new file mode 100644
index 00000000..396a7bc8
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/230_reg_bad_fail_stale_true.py
@@ -0,0 +1,41 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# In this test we simulate broken server, where it always sends
+# stale=true with all 401 responses. We should expect pjsip to
+# retry the authentication until PJSIP_MAX_STALE_COUNT is
+# exceeded. When pjsip retries the authentication, it should
+# use the new nonce from server
+
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--realm=python --user=username --password=password"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+ )
+
+req2 = sip.RecvfromTransaction("First retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"1\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"2\", stale=true"]
+ )
+
+req3 = sip.RecvfromTransaction("Second retry retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"2\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"3\", stale=true"]
+ )
+
+req4 = sip.RecvfromTransaction("Third retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"3\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"4\", stale=true"],
+ expect="PJSIP_EAUTHSTALECOUNT"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Failed registration retry (server rejects with stale=true) ",
+ pjsua, [req1, req2, req3, req4])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/231_reg_bad_fail_stale_false_nonce_changed.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/231_reg_bad_fail_stale_false_nonce_changed.py
new file mode 100644
index 00000000..ab0798d8
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/231_reg_bad_fail_stale_false_nonce_changed.py
@@ -0,0 +1,41 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# In this test we simulate broken server, where:
+# - it wants to signal that NONCE has change
+# - but it sets stale=false
+# For this case pjsip will retry authentication until
+# PJSIP_MAX_STALE_COUNT is exceeded.
+#
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--realm=python --user=username --password=password"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+ )
+
+req2 = sip.RecvfromTransaction("First retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"1\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"2\", stale=true"]
+ )
+
+req3 = sip.RecvfromTransaction("Second retry retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"2\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"3\", stale=true"]
+ )
+
+req4 = sip.RecvfromTransaction("Third retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"3\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"4\", stale=true"],
+ expect="PJSIP_EAUTHSTALECOUNT"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Failed registration retry (server rejects with stale=true) ",
+ pjsua, [req1, req2, req3, req4])
diff --git a/pjsip-apps/src/test-pjsua/scripts-recvfrom/234_reg_bad_stale_ok.py b/pjsip-apps/src/test-pjsua/scripts-recvfrom/234_reg_bad_stale_ok.py
new file mode 100644
index 00000000..1be92c5f
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-recvfrom/234_reg_bad_stale_ok.py
@@ -0,0 +1,41 @@
+# $Id:$
+import inc_sip as sip
+import inc_sdp as sdp
+
+# In this test we simulate broken server, where it wants to
+# change the nonce, but it fails to set stale to true. In this
+# case, we should expect pjsip to retry the authentication until
+# PJSIP_MAX_STALE_COUNT is exceeded as it should have detected
+# that that nonce has changed
+
+
+pjsua = "--null-audio --id=sip:CLIENT --registrar sip:127.0.0.1:$PORT " + \
+ "--realm=python --user=username --password=password " + \
+ "--auto-update-nat=0"
+
+req1 = sip.RecvfromTransaction("Initial request", 401,
+ include=["REGISTER sip"],
+ exclude=["Authorization"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"1\""]
+ )
+
+req2 = sip.RecvfromTransaction("First retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"1\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"2\""]
+ )
+
+req3 = sip.RecvfromTransaction("Second retry retry", 401,
+ include=["REGISTER sip", "Authorization", "nonce=\"2\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ resp_hdr=["WWW-Authenticate: Digest realm=\"python\", nonce=\"3\""]
+ )
+
+req4 = sip.RecvfromTransaction("Third retry", 200,
+ include=["REGISTER sip", "Authorization", "nonce=\"3\""],
+ exclude=["Authorization:[\\s\\S]+Authorization:"],
+ expect="registration success"
+ )
+
+recvfrom_cfg = sip.RecvfromCfg("Successful auth server changes nonce but with stale=false",
+ pjsua, [req1, req2, req3, req4])