Benny Prijono | 632be0a | 2008-06-26 19:51:01 +0000 | [diff] [blame] | 1 | # $Id$ |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 2 | # |
| 3 | from socket import * |
| 4 | import re |
| 5 | import random |
| 6 | import time |
| 7 | import sys |
| 8 | import inc_cfg as cfg |
| 9 | from select import * |
| 10 | |
| 11 | # SIP request template |
| 12 | req_templ = \ |
| 13 | """$METHOD $TARGET_URI SIP/2.0\r |
| 14 | Via: SIP/2.0/UDP $LOCAL_IP:$LOCAL_PORT;rport;branch=z9hG4bK$BRANCH\r |
| 15 | Max-Forwards: 70\r |
| 16 | From: <sip:caller@pjsip.org>$FROM_TAG\r |
| 17 | To: <$TARGET_URI>$TO_TAG\r |
| 18 | Contact: <sip:$LOCAL_IP:$LOCAL_PORT;transport=udp>\r |
| 19 | Call-ID: $CALL_ID@pjsip.org\r |
| 20 | CSeq: $CSEQ $METHOD\r |
| 21 | Allow: PRACK, INVITE, ACK, BYE, CANCEL, UPDATE, REFER\r |
| 22 | Supported: replaces, 100rel, norefersub\r |
| 23 | User-Agent: pjsip.org Python tester\r |
| 24 | Content-Length: $CONTENT_LENGTH\r |
| 25 | $SIP_HEADERS""" |
| 26 | |
| 27 | |
| 28 | def is_request(msg): |
| 29 | return msg.split(" ", 1)[0] != "SIP/2.0" |
| 30 | |
| 31 | def is_response(msg): |
| 32 | return msg.split(" ", 1)[0] == "SIP/2.0" |
| 33 | |
| 34 | def get_code(msg): |
| 35 | if msg=="": |
| 36 | return 0 |
| 37 | return int(msg.split(" ", 2)[1]) |
| 38 | |
| 39 | def get_tag(msg, hdr="To"): |
| 40 | pat = "^" + hdr + ":.*" |
| 41 | result = re.search(pat, msg, re.M | re.I) |
| 42 | if result==None: |
| 43 | return "" |
| 44 | line = result.group() |
| 45 | #print "line=", line |
| 46 | tags = line.split(";tag=") |
| 47 | if len(tags)>1: |
| 48 | return tags[1] |
| 49 | return "" |
| 50 | #return re.split("[;& ]", s) |
| 51 | |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 52 | def get_header(msg, hname): |
| 53 | headers = msg.splitlines() |
| 54 | for hdr in headers: |
| 55 | hfields = hdr.split(": ", 2) |
| 56 | if hfields[0]==hname: |
| 57 | return hfields[1] |
| 58 | return None |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 59 | |
| 60 | class Dialog: |
| 61 | sock = None |
| 62 | dst_addr = "" |
| 63 | dst_port = 5060 |
| 64 | local_ip = "" |
| 65 | local_port = 0 |
| 66 | tcp = False |
| 67 | call_id = str(random.random()) |
| 68 | cseq = 0 |
| 69 | local_tag = ";tag=" + str(random.random()) |
| 70 | rem_tag = "" |
| 71 | last_resp_code = 0 |
| 72 | inv_branch = "" |
| 73 | trace_enabled = True |
| 74 | last_request = "" |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 75 | def __init__(self, dst_addr, dst_port=5060, tcp=False, trace=True, local_port=0): |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 76 | self.dst_addr = dst_addr |
| 77 | self.dst_port = dst_port |
| 78 | self.tcp = tcp |
| 79 | self.trace_enabled = trace |
| 80 | if tcp==True: |
| 81 | self.sock = socket(AF_INET, SOCK_STREAM) |
| 82 | self.sock.connect(dst_addr, dst_port) |
| 83 | else: |
| 84 | self.sock = socket(AF_INET, SOCK_DGRAM) |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 85 | self.sock.bind(("127.0.0.1", local_port)) |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 86 | |
| 87 | self.local_ip, self.local_port = self.sock.getsockname() |
| 88 | self.trace("Dialog socket bound to " + self.local_ip + ":" + str(self.local_port)) |
| 89 | |
| 90 | def trace(self, txt): |
| 91 | if self.trace_enabled: |
| 92 | print str(time.strftime("%H:%M:%S ")) + txt |
| 93 | |
| 94 | def create_req(self, method, sdp, branch="", extra_headers=""): |
| 95 | if branch=="": |
| 96 | self.cseq = self.cseq + 1 |
| 97 | msg = req_templ |
| 98 | msg = msg.replace("$METHOD", method) |
| 99 | if self.tcp: |
| 100 | transport_param = ";transport=tcp" |
| 101 | else: |
| 102 | transport_param = "" |
| 103 | msg = msg.replace("$TARGET_URI", "sip:"+self.dst_addr+":"+str(self.dst_port) + transport_param) |
| 104 | msg = msg.replace("$LOCAL_IP", self.local_ip) |
| 105 | msg = msg.replace("$LOCAL_PORT", str(self.local_port)) |
| 106 | if branch=="": |
| 107 | branch=str(random.random()) |
| 108 | msg = msg.replace("$BRANCH", branch) |
| 109 | msg = msg.replace("$FROM_TAG", self.local_tag) |
| 110 | msg = msg.replace("$TO_TAG", self.rem_tag) |
| 111 | msg = msg.replace("$CALL_ID", self.call_id) |
| 112 | msg = msg.replace("$CSEQ", str(self.cseq)) |
| 113 | msg = msg.replace("$SIP_HEADERS", extra_headers) |
| 114 | if sdp!="": |
| 115 | msg = msg.replace("$CONTENT_LENGTH", str(len(sdp))) |
| 116 | msg = msg + "Content-Type: application/sdp\r\n" |
| 117 | else: |
| 118 | msg = msg.replace("$CONTENT_LENGTH", "0") |
| 119 | msg = msg + "\r\n" |
| 120 | msg = msg + sdp |
| 121 | return msg |
| 122 | |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 123 | def create_response(self, request, code, reason, to_tag=""): |
| 124 | response = "SIP/2.0 " + str(code) + " " + reason + "\r\n" |
| 125 | lines = request.splitlines() |
| 126 | for line in lines: |
| 127 | hdr = line.split(":", 1)[0] |
| 128 | if hdr in ["Via", "From", "To", "CSeq", "Call-ID"]: |
| 129 | if hdr=="To" and to_tag!="": |
| 130 | line = line + ";tag=" + to_tag |
| 131 | elif hdr=="Via": |
| 132 | line = line + ";received=127.0.0.1" |
| 133 | response = response + line + "\r\n" |
| 134 | return response |
| 135 | |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 136 | def create_invite(self, sdp, extra_headers=""): |
| 137 | self.inv_branch = str(random.random()) |
| 138 | return self.create_req("INVITE", sdp, branch=self.inv_branch, extra_headers=extra_headers) |
| 139 | |
| 140 | def create_ack(self, sdp="", extra_headers=""): |
| 141 | return self.create_req("ACK", sdp, extra_headers=extra_headers, branch=self.inv_branch) |
| 142 | |
| 143 | def create_bye(self, extra_headers=""): |
| 144 | return self.create_req("BYE", "", extra_headers) |
| 145 | |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 146 | def send_msg(self, msg, dst_addr=None): |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 147 | if (is_request(msg)): |
| 148 | self.last_request = msg.split(" ", 1)[0] |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 149 | if not dst_addr: |
| 150 | dst_addr = (self.dst_addr, self.dst_port) |
| 151 | self.trace("============== TX MSG to " + str(dst_addr) + " ============= \n" + msg) |
| 152 | self.sock.sendto(msg, 0, dst_addr) |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 153 | |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 154 | def wait_msg_from(self, timeout): |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 155 | endtime = time.time() + timeout |
| 156 | msg = "" |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 157 | src_addr = None |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 158 | while time.time() < endtime: |
| 159 | readset = select([self.sock], [], [], timeout) |
| 160 | if len(readset) < 1 or not self.sock in readset[0]: |
| 161 | if len(readset) < 1: |
| 162 | print "select() returns " + str(len(readset)) |
| 163 | elif not self.sock in readset[0]: |
| 164 | print "select() alien socket" |
| 165 | else: |
| 166 | print "select other error" |
| 167 | continue |
| 168 | try: |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 169 | msg, src_addr = self.sock.recvfrom(2048) |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 170 | except: |
| 171 | print "recv() exception: ", sys.exc_info()[0] |
| 172 | continue |
| 173 | |
| 174 | if msg=="": |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 175 | return "", None |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 176 | if self.last_request=="INVITE" and self.rem_tag=="": |
| 177 | self.rem_tag = get_tag(msg, "To") |
| 178 | self.rem_tag = self.rem_tag.rstrip("\r\n;") |
| 179 | if self.rem_tag != "": |
| 180 | self.rem_tag = ";tag=" + self.rem_tag |
| 181 | self.trace("=== rem_tag:" + self.rem_tag) |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 182 | self.trace("=========== RX MSG from " + str(src_addr) + " ===========\n" + msg) |
| 183 | return (msg, src_addr) |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 184 | |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 185 | def wait_msg(self, timeout): |
| 186 | return self.wait_msg_from(timeout)[0] |
| 187 | |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 188 | # Send request and wait for final response |
| 189 | def send_request_wait(self, msg, timeout): |
| 190 | t1 = 1.0 |
| 191 | endtime = time.time() + timeout |
| 192 | resp = "" |
| 193 | code = 0 |
| 194 | for i in range(0,5): |
| 195 | self.send_msg(msg) |
| 196 | resp = self.wait_msg(t1) |
| 197 | if resp!="" and is_response(resp): |
| 198 | code = get_code(resp) |
| 199 | break |
| 200 | last_resp = resp |
| 201 | while code < 200 and time.time() < endtime: |
| 202 | resp = self.wait_msg(endtime - time.time()) |
| 203 | if resp != "" and is_response(resp): |
| 204 | code = get_code(resp) |
| 205 | last_resp = resp |
| 206 | elif resp=="": |
| 207 | break |
| 208 | return last_resp |
| 209 | |
| 210 | def hangup(self, last_code=0): |
| 211 | self.trace("====== hangup =====") |
| 212 | if last_code!=0: |
| 213 | self.last_resp_code = last_code |
| 214 | if self.last_resp_code>0 and self.last_resp_code<200: |
| 215 | msg = self.create_req("CANCEL", "", branch=self.inv_branch, extra_headers="") |
| 216 | self.send_request_wait(msg, 5) |
| 217 | msg = self.create_ack() |
| 218 | self.send_msg(msg) |
| 219 | elif self.last_resp_code>=200 and self.last_resp_code<300: |
| 220 | msg = self.create_ack() |
| 221 | self.send_msg(msg) |
| 222 | msg = self.create_bye() |
| 223 | self.send_request_wait(msg, 5) |
| 224 | else: |
| 225 | msg = self.create_ack() |
| 226 | self.send_msg(msg) |
| 227 | |
| 228 | |
| 229 | class SendtoCfg: |
| 230 | # Test name |
| 231 | name = "" |
| 232 | # pjsua InstanceParam |
| 233 | inst_param = None |
Benny Prijono | 036911b | 2008-06-27 21:22:12 +0000 | [diff] [blame] | 234 | # Complete INVITE message. If this is not empty, then this |
| 235 | # message will be sent instead and the "sdp" and "extra_headers" |
| 236 | # settings will be ignored. |
| 237 | complete_msg = "" |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 238 | # Initial SDP |
| 239 | sdp = "" |
Benny Prijono | 632be0a | 2008-06-26 19:51:01 +0000 | [diff] [blame] | 240 | # Extra headers to add to request |
| 241 | extra_headers = "" |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 242 | # Expected code |
| 243 | resp_code = 0 |
| 244 | # Use TCP? |
| 245 | use_tcp = False |
| 246 | # List of RE patterns that must exist in response |
| 247 | resp_include = [] |
| 248 | # List of RE patterns that must NOT exist in response |
| 249 | resp_exclude = [] |
| 250 | # Constructor |
Benny Prijono | 632be0a | 2008-06-26 19:51:01 +0000 | [diff] [blame] | 251 | def __init__(self, name, pjsua_args, sdp, resp_code, |
| 252 | resp_inc=[], resp_exc=[], use_tcp=False, |
Benny Prijono | 1e65e9a | 2008-06-27 23:53:00 +0000 | [diff] [blame] | 253 | extra_headers="", complete_msg="", |
| 254 | enable_buffer = False): |
Benny Prijono | 036911b | 2008-06-27 21:22:12 +0000 | [diff] [blame] | 255 | self.complete_msg = complete_msg |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 256 | self.sdp = sdp |
| 257 | self.resp_code = resp_code |
| 258 | self.resp_include = resp_inc |
| 259 | self.resp_exclude = resp_exc |
| 260 | self.use_tcp = use_tcp |
Benny Prijono | 632be0a | 2008-06-26 19:51:01 +0000 | [diff] [blame] | 261 | self.extra_headers = extra_headers |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 262 | self.inst_param = cfg.InstanceParam("pjsua", pjsua_args) |
Benny Prijono | 1e65e9a | 2008-06-27 23:53:00 +0000 | [diff] [blame] | 263 | self.inst_param.enable_buffer = enable_buffer |
Benny Prijono | 7d578a7 | 2008-06-20 00:25:55 +0000 | [diff] [blame] | 264 | |
Benny Prijono | e9a8224 | 2008-07-07 20:14:41 +0000 | [diff] [blame] | 265 | |
| 266 | class RecvfromTransaction: |
| 267 | # The test title for this transaction |
| 268 | title = "" |
| 269 | # Optinal list of pjsua command and optional expect patterns |
| 270 | # to be invoked to make pjsua send a request |
| 271 | # Sample: |
| 272 | # (to make call and wait for INVITE to be sent) |
| 273 | # cmds = [ ["m"], ["sip:127.0.0.1", "INVITE sip:"] ] |
| 274 | cmds = [] |
| 275 | # Check if the CSeq must be greater than last Cseq? |
| 276 | check_cseq = True |
| 277 | # List of RE patterns that must exists in incoming request |
| 278 | include = [] |
| 279 | # List of RE patterns that MUST NOT exist in incoming request |
| 280 | exclude = [] |
| 281 | # Response code to send |
| 282 | resp_code = 0 |
| 283 | # Additional list of headers to be sent on the response |
| 284 | # Note: no need to add CRLF on the header |
| 285 | resp_hdr = [] |
| 286 | # Message body. This should include the Content-Type header too. |
| 287 | # Sample: |
| 288 | # body = """Content-Type: application/sdp\r\n |
| 289 | # \r\n |
| 290 | # v=0\r\n |
| 291 | # ... |
| 292 | # """ |
| 293 | body = None |
| 294 | # Pattern to be expected on pjsua when receiving the response |
| 295 | expect = "" |
| 296 | |
| 297 | def __init__(self, title, resp_code, check_cseq=True, |
| 298 | include=[], exclude=[], cmds=[], resp_hdr=[], resp_body=None, expect=""): |
| 299 | self.title = title |
| 300 | self.cmds = cmds |
| 301 | self.include = include |
| 302 | self.exclude = exclude |
| 303 | self.resp_code = resp_code |
| 304 | self.resp_hdr = resp_hdr |
| 305 | self.resp_body = resp_body |
| 306 | self.expect = expect |
| 307 | |
| 308 | |
| 309 | class RecvfromCfg: |
| 310 | # Test name |
| 311 | name = "" |
| 312 | # pjsua InstanceParam |
| 313 | inst_param = None |
| 314 | # List of RecvfromTransaction |
| 315 | transaction = None |
| 316 | # Use TCP? |
| 317 | tcp = False |
| 318 | |
| 319 | # Note: |
| 320 | # Any "$PORT" string in the pjsua_args will be replaced |
| 321 | # by server port |
| 322 | def __init__(self, name, pjsua_args, transaction, tcp=False): |
| 323 | self.name = name |
| 324 | self.inst_param = cfg.InstanceParam("pjsua", pjsua_args) |
| 325 | self.transaction = transaction |
| 326 | self.tcp=tcp |
| 327 | |
| 328 | |
| 329 | |
| 330 | |