Add support for ICE/STUN/TURN in res_rtp_asterisk and chan_sip.
[asterisk/asterisk.git] / res / pjproject / tests / pjsua / inc_sip.py
1 # $Id$
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
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
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 = ""
75         def __init__(self, dst_addr, dst_port=5060, tcp=False, trace=True, local_port=0):
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)
85                         self.sock.bind(("127.0.0.1", local_port))
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 update_fields(self, msg):
95                 if self.tcp:
96                         transport_param = ";transport=tcp"
97                 else:
98                         transport_param = ""
99                 msg = msg.replace("$TARGET_URI", "sip:"+self.dst_addr+":"+str(self.dst_port) + transport_param)
100                 msg = msg.replace("$LOCAL_IP", self.local_ip)
101                 msg = msg.replace("$LOCAL_PORT", str(self.local_port))
102                 msg = msg.replace("$FROM_TAG", self.local_tag)
103                 msg = msg.replace("$TO_TAG", self.rem_tag)
104                 msg = msg.replace("$CALL_ID", self.call_id)
105                 msg = msg.replace("$CSEQ", str(self.cseq))
106                 branch=str(random.random())
107                 msg = msg.replace("$BRANCH", branch)
108                 return msg
109
110         def create_req(self, method, sdp, branch="", extra_headers="", body=""):
111                 if branch=="":
112                         self.cseq = self.cseq + 1
113                 msg = req_templ
114                 msg = msg.replace("$METHOD", method)
115                 msg = msg.replace("$SIP_HEADERS", extra_headers)
116                 if branch=="":
117                         branch=str(random.random())
118                 msg = msg.replace("$BRANCH", branch)
119                 if sdp!="":
120                         msg = msg.replace("$CONTENT_LENGTH", str(len(sdp)))
121                         msg = msg + "Content-Type: application/sdp\r\n"
122                         msg = msg + "\r\n"
123                         msg = msg + sdp
124                 elif body!="":
125                         msg = msg.replace("$CONTENT_LENGTH", str(len(body)))
126                         msg = msg + "\r\n"
127                         msg = msg + body
128                 else:
129                         msg = msg.replace("$CONTENT_LENGTH", "0")
130                 return self.update_fields(msg)
131
132         def create_response(self, request, code, reason, to_tag=""):
133                 response = "SIP/2.0 " + str(code) + " " + reason + "\r\n"
134                 lines = request.splitlines()
135                 for line in lines:
136                         hdr = line.split(":", 1)[0]
137                         if hdr in ["Via", "From", "To", "CSeq", "Call-ID"]:
138                                 if hdr=="To" and to_tag!="":
139                                         line = line + ";tag=" + to_tag
140                                 elif hdr=="Via":
141                                         line = line + ";received=127.0.0.1"
142                                 response = response + line + "\r\n"
143                 return response
144
145         def create_invite(self, sdp, extra_headers="", body=""):
146                 self.inv_branch = str(random.random())
147                 return self.create_req("INVITE", sdp, branch=self.inv_branch, extra_headers=extra_headers, body=body)
148
149         def create_ack(self, sdp="", extra_headers=""):
150                 return self.create_req("ACK", sdp, extra_headers=extra_headers, branch=self.inv_branch)
151
152         def create_bye(self, extra_headers=""):
153                 return self.create_req("BYE", "", extra_headers)
154
155         def send_msg(self, msg, dst_addr=None):
156                 if (is_request(msg)):
157                         self.last_request = msg.split(" ", 1)[0]
158                 if not dst_addr:
159                         dst_addr = (self.dst_addr, self.dst_port)
160                 self.trace("============== TX MSG to " + str(dst_addr) + " ============= \n" + msg)
161                 self.sock.sendto(msg, 0, dst_addr)
162
163         def wait_msg_from(self, timeout):
164                 endtime = time.time() + timeout
165                 msg = ""
166                 src_addr = None
167                 while time.time() < endtime:
168                         readset = select([self.sock], [], [], 1)
169                         if len(readset[0]) < 1 or not self.sock in readset[0]:
170                                 if len(readset[0]) < 1:
171                                         print "select() timeout (will wait for " + str(int(endtime - time.time())) + "more secs)"
172                                 elif not self.sock in readset[0]:
173                                         print "select() alien socket"
174                                 else:
175                                         print "select other error"
176                                 continue
177                         try:
178                                 msg, src_addr = self.sock.recvfrom(4096)
179                                 break
180                         except:
181                                 print "recv() exception: ", sys.exc_info()[0]
182                                 continue
183
184                 if msg=="":
185                         return "", None
186                 if self.last_request=="INVITE" and self.rem_tag=="":
187                         self.rem_tag = get_tag(msg, "To")
188                         self.rem_tag = self.rem_tag.rstrip("\r\n;")
189                         if self.rem_tag != "":
190                                 self.rem_tag = ";tag=" + self.rem_tag
191                         self.trace("=== rem_tag:" + self.rem_tag)
192                 self.trace("=========== RX MSG from " + str(src_addr) +  " ===========\n" + msg)
193                 return (msg, src_addr)
194         
195         def wait_msg(self, timeout):
196                 return self.wait_msg_from(timeout)[0]
197                 
198         # Send request and wait for final response
199         def send_request_wait(self, msg, timeout):
200                 t1 = 1.0
201                 endtime = time.time() + timeout
202                 resp = ""
203                 code = 0
204                 for i in range(0,5):
205                         self.send_msg(msg)
206                         resp = self.wait_msg(t1)
207                         if resp!="" and is_response(resp):
208                                 code = get_code(resp)
209                                 break
210                 last_resp = resp
211                 while code < 200 and time.time() < endtime:
212                         resp = self.wait_msg(endtime - time.time())
213                         if resp != "" and is_response(resp):
214                                 code = get_code(resp)
215                                 last_resp = resp
216                         elif resp=="":
217                                 break
218                 return last_resp
219         
220         def hangup(self, last_code=0):
221                 self.trace("====== hangup =====")
222                 if last_code!=0:
223                         self.last_resp_code = last_code
224                 if self.last_resp_code>0 and self.last_resp_code<200:
225                         msg = self.create_req("CANCEL", "", branch=self.inv_branch, extra_headers="")
226                         self.send_request_wait(msg, 5)
227                         msg = self.create_ack()
228                         self.send_msg(msg)
229                 elif self.last_resp_code>=200 and self.last_resp_code<300:
230                         msg = self.create_ack()
231                         self.send_msg(msg)
232                         msg = self.create_bye()
233                         self.send_request_wait(msg, 5)
234                 else:
235                         msg = self.create_ack()
236                         self.send_msg(msg)
237
238
239 class SendtoCfg:
240         # Test name
241         name = ""
242         # pjsua InstanceParam
243         inst_param = None
244         # Complete INVITE message. If this is not empty, then this
245         # message will be sent instead and the "sdp" and "extra_headers"
246         # settings will be ignored.
247         complete_msg = ""
248         # Initial SDP
249         sdp = ""
250         # Extra headers to add to request
251         extra_headers = ""
252         # Expected code
253         resp_code = 0
254         # Use TCP?
255         use_tcp = False
256         # List of RE patterns that must exist in response
257         resp_include = []
258         # List of RE patterns that must NOT exist in response
259         resp_exclude = []
260         # Full (non-SDP) body
261         body = ""
262         # Constructor
263         def __init__(self, name, pjsua_args, sdp, resp_code, 
264                      resp_inc=[], resp_exc=[], use_tcp=False,
265                      extra_headers="", body="", complete_msg="",
266                      enable_buffer = False):
267                 self.complete_msg = complete_msg
268                 self.sdp = sdp
269                 self.resp_code = resp_code
270                 self.resp_include = resp_inc
271                 self.resp_exclude = resp_exc
272                 self.use_tcp = use_tcp
273                 self.extra_headers = extra_headers
274                 self.body = body
275                 self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
276                 self.inst_param.enable_buffer = enable_buffer 
277
278
279 class RecvfromTransaction:
280         # The test title for this transaction
281         title = ""
282         # Optinal list of pjsua command and optional expect patterns 
283         # to be invoked to make pjsua send a request
284         # Sample:
285         #       (to make call and wait for INVITE to be sent)
286         #       cmds = [ ["m"], ["sip:127.0.0.1", "INVITE sip:"]  ]
287         cmds = []
288         # Check if the CSeq must be greater than last Cseq?
289         check_cseq = True
290         # List of RE patterns that must exists in incoming request
291         include = []
292         # List of RE patterns that MUST NOT exist in incoming request
293         exclude = []
294         # Response code to send
295         resp_code = 0
296         # Additional list of headers to be sent on the response
297         # Note: no need to add CRLF on the header
298         resp_hdr = []
299         # Message body. This should include the Content-Type header too.
300         # Sample:
301         #       body = """Content-Type: application/sdp\r\n
302         #       \r\n
303         #       v=0\r\n
304         #       ...
305         #       """
306         body = None
307         # Pattern to be expected on pjsua when receiving the response
308         expect = ""
309         
310         def __init__(self, title, resp_code, check_cseq=True,
311                         include=[], exclude=[], cmds=[], resp_hdr=[], resp_body=None, expect=""):
312                 self.title = title
313                 self.cmds = cmds
314                 self.include = include
315                 self.exclude = exclude
316                 self.resp_code = resp_code
317                 self.resp_hdr = resp_hdr
318                 self.body = resp_body
319                 self.expect = expect
320                         
321
322 class RecvfromCfg:
323         # Test name
324         name = ""
325         # pjsua InstanceParam
326         inst_param = None
327         # List of RecvfromTransaction
328         transaction = None
329         # Use TCP?
330         tcp = False
331
332         # Note:
333         #  Any "$PORT" string in the pjsua_args will be replaced
334         #  by server port
335         def __init__(self, name, pjsua_args, transaction, tcp=False):
336                 self.name = name
337                 self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
338                 self.transaction = transaction
339                 self.tcp=tcp
340
341
342
343