1
# $Id: inc_sip.py 3283 2010-08-18 07:37:29Z bennylp $
11
# SIP request template
13
"""$METHOD $TARGET_URI SIP/2.0\r
14
Via: SIP/2.0/UDP $LOCAL_IP:$LOCAL_PORT;rport;branch=z9hG4bK$BRANCH\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
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
29
return msg.split(" ", 1)[0] != "SIP/2.0"
32
return msg.split(" ", 1)[0] == "SIP/2.0"
37
return int(msg.split(" ", 2)[1])
39
def get_tag(msg, hdr="To"):
40
pat = "^" + hdr + ":.*"
41
result = re.search(pat, msg, re.M | re.I)
46
tags = line.split(";tag=")
50
#return re.split("[;& ]", s)
52
def get_header(msg, hname):
53
headers = msg.splitlines()
55
hfields = hdr.split(": ", 2)
67
call_id = str(random.random())
69
local_tag = ";tag=" + str(random.random())
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
79
self.trace_enabled = trace
81
self.sock = socket(AF_INET, SOCK_STREAM)
82
self.sock.connect(dst_addr, dst_port)
84
self.sock = socket(AF_INET, SOCK_DGRAM)
85
self.sock.bind(("127.0.0.1", local_port))
87
self.local_ip, self.local_port = self.sock.getsockname()
88
self.trace("Dialog socket bound to " + self.local_ip + ":" + str(self.local_port))
91
if self.trace_enabled:
92
print str(time.strftime("%H:%M:%S ")) + txt
94
def update_fields(self, msg):
96
transport_param = ";transport=tcp"
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)
110
def create_req(self, method, sdp, branch="", extra_headers="", body=""):
112
self.cseq = self.cseq + 1
114
msg = msg.replace("$METHOD", method)
115
msg = msg.replace("$SIP_HEADERS", extra_headers)
117
branch=str(random.random())
118
msg = msg.replace("$BRANCH", branch)
120
msg = msg.replace("$CONTENT_LENGTH", str(len(sdp)))
121
msg = msg + "Content-Type: application/sdp\r\n"
125
msg = msg.replace("$CONTENT_LENGTH", str(len(body)))
129
msg = msg.replace("$CONTENT_LENGTH", "0")
130
return self.update_fields(msg)
132
def create_response(self, request, code, reason, to_tag=""):
133
response = "SIP/2.0 " + str(code) + " " + reason + "\r\n"
134
lines = request.splitlines()
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
141
line = line + ";received=127.0.0.1"
142
response = response + line + "\r\n"
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)
149
def create_ack(self, sdp="", extra_headers=""):
150
return self.create_req("ACK", sdp, extra_headers=extra_headers, branch=self.inv_branch)
152
def create_bye(self, extra_headers=""):
153
return self.create_req("BYE", "", extra_headers)
155
def send_msg(self, msg, dst_addr=None):
156
if (is_request(msg)):
157
self.last_request = msg.split(" ", 1)[0]
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)
163
def wait_msg_from(self, timeout):
164
endtime = time.time() + timeout
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"
175
print "select other error"
178
msg, src_addr = self.sock.recvfrom(4096)
181
print "recv() exception: ", sys.exc_info()[0]
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)
195
def wait_msg(self, timeout):
196
return self.wait_msg_from(timeout)[0]
198
# Send request and wait for final response
199
def send_request_wait(self, msg, timeout):
201
endtime = time.time() + timeout
206
resp = self.wait_msg(t1)
207
if resp!="" and is_response(resp):
208
code = get_code(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)
220
def hangup(self, last_code=0):
221
self.trace("====== hangup =====")
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()
229
elif self.last_resp_code>=200 and self.last_resp_code<300:
230
msg = self.create_ack()
232
msg = self.create_bye()
233
self.send_request_wait(msg, 5)
235
msg = self.create_ack()
242
# pjsua InstanceParam
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.
250
# Extra headers to add to request
256
# List of RE patterns that must exist in response
258
# List of RE patterns that must NOT exist in response
260
# Full (non-SDP) body
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
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
275
self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
276
self.inst_param.enable_buffer = enable_buffer
279
class RecvfromTransaction:
280
# The test title for this transaction
282
# Optinal list of pjsua command and optional expect patterns
283
# to be invoked to make pjsua send a request
285
# (to make call and wait for INVITE to be sent)
286
# cmds = [ ["m"], ["sip:127.0.0.1", "INVITE sip:"] ]
288
# Check if the CSeq must be greater than last Cseq?
290
# List of RE patterns that must exists in incoming request
292
# List of RE patterns that MUST NOT exist in incoming request
294
# Response code to send
296
# Additional list of headers to be sent on the response
297
# Note: no need to add CRLF on the header
299
# Message body. This should include the Content-Type header too.
301
# body = """Content-Type: application/sdp\r\n
307
# Pattern to be expected on pjsua when receiving the response
310
def __init__(self, title, resp_code, check_cseq=True,
311
include=[], exclude=[], cmds=[], resp_hdr=[], resp_body=None, expect=""):
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
325
# pjsua InstanceParam
327
# List of RecvfromTransaction
333
# Any "$PORT" string in the pjsua_args will be replaced
335
def __init__(self, name, pjsua_args, transaction, tcp=False):
337
self.inst_param = cfg.InstanceParam("pjsua", pjsua_args)
338
self.transaction = transaction