quaff 0.7.3 → 0.8.0pre1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dd835927c3eee7a4ba02cd376b8967d0cea3c5e8
4
- data.tar.gz: bad0aed037d35fbbae1996f95e7145dab1d43543
3
+ metadata.gz: 3c2cf0abc7f41e6977f8bec2c36a4c7f614cf319
4
+ data.tar.gz: 58d82abdf2ea57652f5fa14d81ce1fbbbaf770be
5
5
  SHA512:
6
- metadata.gz: 9e6aeafc9f296e0718b366ea52eb17943d79ccb207515d5ee85f9e128ef3fe7eaca03f3ca63a450beb46edf30ca04fdfe0505153e1c3cb23bfcd9ae2e7d3d0a9
7
- data.tar.gz: e8ecea0abae1644e6494dcd9a3aadd1d647cbb479b7e2be29fbb2f1a27b235515ec4bfa37971e0335abaad245cac53be96d243d3447faf964d0a4f98c394455b
6
+ metadata.gz: c3e44bb218bf436fc72fefb1c1113c3eb52ddea5694706fdfccf7e2d9c5a7bf7d7c12940b08ca2ee4252f0cdad87415bade59eea460cad27cecdbca8a34cd8d6
7
+ data.tar.gz: 2f696985ca1e300e8a61eda9ea89c7a239e05688db511cfbd7b03c8bcac634ca22889621d5f174c19e73fbe605f2fe3358415e303d63ac0726a5b1254ff7d1db
data/lib/quaff.rb CHANGED
@@ -1,3 +1,3 @@
1
- require_relative './call.rb'
2
- require_relative './endpoint.rb'
1
+ require_relative './quaff/call.rb'
2
+ require_relative './quaff/endpoint.rb'
3
3
 
@@ -2,18 +2,14 @@ require 'base64'
2
2
 
3
3
  module Quaff
4
4
  module Auth #:nodoc:
5
- def Auth.gen_nonce auth_pairs, username, passwd, method, sip_uri, ha1="", qop="", nc=1, cnonce=""
5
+ def Auth.gen_nonce auth_pairs, username, passwd, method, sip_uri, ha1=""
6
6
  if ha1.empty?
7
7
  a1 = username + ":" + auth_pairs["realm"] + ":" + passwd
8
8
  ha1 = Digest::MD5::hexdigest(a1)
9
9
  end
10
10
  a2 = method + ":" + sip_uri
11
11
  ha2 = Digest::MD5::hexdigest(a2)
12
- if qop == "auth"
13
- digest = Digest::MD5.hexdigest(ha1 + ":" + auth_pairs["nonce"] + ":" + nc.to_s(16).rjust(8, "0") + ":" + cnonce + ":" + qop + ":" + ha2)
14
- else
15
- digest = Digest::MD5.hexdigest(ha1 + ":" + auth_pairs["nonce"] + ":" + ha2)
16
- end
12
+ digest = Digest::MD5.hexdigest(ha1 + ":" + auth_pairs["nonce"] + ":" + ha2)
17
13
  return digest
18
14
  end
19
15
 
@@ -35,22 +31,15 @@ module Quaff
35
31
  # the SIP URI
36
32
  end
37
33
 
38
- def Auth.gen_auth_header auth_line, username, passwd, method, sip_uri, ha1="", qop="", nc=1, cnonce=""
34
+ def Auth.gen_auth_header auth_line, username, passwd, method, sip_uri, ha1=""
39
35
  # Split auth line on commas
40
36
  auth_pairs = {}
41
37
  auth_line.sub("Digest ", "").split(",") .each do |pair|
42
38
  key, value = pair.split "="
43
39
  auth_pairs[key.gsub(" ", "")] = value.gsub("\"", "").gsub(" ", "")
44
40
  end
45
- if !qop.empty? and cnonce.empty?
46
- cnonce = (0...16).map { (rand(16)).to_s(16) }.join
47
- end
48
- digest = gen_nonce auth_pairs, username, passwd, method, sip_uri, ha1, qop, nc, cnonce
49
- if !qop.empty?
50
- return %Q!Digest username="#{username}",realm="#{auth_pairs['realm']}",nonce="#{auth_pairs['nonce']}",uri="#{sip_uri}",response="#{digest}",algorithm="#{auth_pairs['algorithm']}",opaque="#{auth_pairs['opaque']}",qop="#{qop}",nc="#{nc.to_s(16).rjust(8, "0")}",cnonce="#{cnonce}"!
51
- else
52
- return %Q!Digest username="#{username}",realm="#{auth_pairs['realm']}",nonce="#{auth_pairs['nonce']}",uri="#{sip_uri}",response="#{digest}",algorithm="#{auth_pairs['algorithm']}",opaque="#{auth_pairs['opaque']}"!
53
- end
41
+ digest = gen_nonce auth_pairs, username, passwd, method, sip_uri, ha1
42
+ return %Q!Digest username="#{username}",realm="#{auth_pairs['realm']}",nonce="#{auth_pairs['nonce']}",uri="#{sip_uri}",response="#{digest}",algorithm="#{auth_pairs['algorithm']}",opaque="#{auth_pairs['opaque']}"!
54
43
  # Return Authorization header with fields username, realm, nonce, uri, nc, cnonce, response, opaque
55
44
  end
56
45
  end
data/lib/quaff/call.rb ADDED
@@ -0,0 +1,385 @@
1
+ # -*- coding: us-ascii -*-
2
+ require 'securerandom'
3
+ require 'timeout'
4
+ require_relative './utils.rb'
5
+ require_relative './sources.rb'
6
+ require_relative './auth.rb'
7
+ require_relative './message.rb'
8
+ require_relative './sip_dialog.rb'
9
+
10
+ module Quaff
11
+ class CSeq # :nodoc:
12
+ attr_reader :num
13
+ def initialize cseq_str
14
+ @num, @method = cseq_str.split
15
+ @num = @num.to_i
16
+ end
17
+
18
+ def increment
19
+ @num = @num + 1
20
+ to_s
21
+ end
22
+
23
+ def to_s
24
+ "#{@num.to_s} #{@method}"
25
+ end
26
+ end
27
+
28
+ class Call
29
+ attr_reader :cid, :dialog
30
+
31
+ def initialize(cxn,
32
+ cid,
33
+ my_uri,
34
+ target_uri,
35
+ destination=nil,
36
+ instance_id=nil)
37
+ @cxn = cxn
38
+ setdest(destination, recv_from_this: true) if destination
39
+ @current_retrans = nil
40
+ @retrans_keys = {}
41
+ @t1, @t2 = 0.5, 32
42
+ @instance_id = instance_id
43
+ @dialog = SipDialog.new cid, my_uri, target_uri
44
+ update_branch
45
+ end
46
+
47
+ def set_callee uri
48
+ if /<(.*?)>/ =~ uri
49
+ uri = $1
50
+ end
51
+
52
+ @dialog.target = uri unless uri.nil?
53
+ end
54
+
55
+ alias_method :set_dialog_target, :set_callee
56
+
57
+ # Sets the Source where messages in this call should be sent to by
58
+ # default.
59
+ #
60
+ # Options:
61
+ # :recv_from_this - if true, also listens for any incoming
62
+ # messages over this source's connection. (This is only
63
+ # meaningful for connection-oriented transports.)
64
+ def setdest source, options={}
65
+ @src = source
66
+ if options[:recv_from_this] and source.sock
67
+ @cxn.add_sock source.sock
68
+ end
69
+ end
70
+
71
+ # Receives a SIP request.
72
+ #
73
+ # Options:
74
+ # :dialog_creating - whether the dialog state (peer tags, etc.)
75
+ # should be updated with information from this request. Defaults to true.
76
+ def recv_request(method, options={})
77
+ dialog_creating = options[:dialog_creating] || true
78
+ begin
79
+ msg = recv_something
80
+ rescue Timeout::Error
81
+ raise "#{ @uri } timed out waiting for #{ method }"
82
+ end
83
+
84
+ unless msg.type == :request \
85
+ and Regexp.new(method) =~ msg.method
86
+ raise((msg.to_s || "Message is nil!"))
87
+ end
88
+
89
+ @dialog.cseq = CSeq.new(msg.header("CSeq")).num
90
+
91
+ if dialog_creating
92
+ create_dialog_from_request msg
93
+ end
94
+ msg
95
+ end
96
+
97
+ # Waits until the next message comes in, and handles it if it is one
98
+ # of possible_messages.
99
+ #
100
+ # possible_messages is a list of things that can be received.
101
+ # Elements can be:
102
+ # * a string representing the SIP method, e.g. "INVITE"
103
+ # * a number representing the SIP status code, e.g. 200
104
+ # * a two-item list, containing one of the above and a boolean
105
+ # value, which indicates whether this message is dialog-creating. by
106
+ # default, requests are assumed to be dialog-creating and responses
107
+ # are not.
108
+ #
109
+ # For example, ["INVITE", 301, ["ACK", false], [200, true]] is a
110
+ # valid value for possible_messages.
111
+ def recv_any_of(possible_messages)
112
+ begin
113
+ msg = recv_something
114
+ rescue Timeout::Error
115
+ raise "#{ @uri } timed out waiting for one of these: #{possible_messages}"
116
+ end
117
+
118
+ found_match = false
119
+ dialog_creating = nil
120
+
121
+ possible_messages.each do
122
+ | what, this_dialog_creating |
123
+ type = if (what.class == String) then :request else :response end
124
+ if this_dialog_creating.nil?
125
+ this_dialog_creating = (type == :request)
126
+ end
127
+
128
+ found_match =
129
+ if type == :request
130
+ msg.type == :request and what == msg.method
131
+ else
132
+ msg.type == :response and what.to_s == msg.status_code
133
+ end
134
+
135
+ if found_match
136
+ dialog_creating = this_dialog_creating
137
+ break
138
+ end
139
+ end
140
+
141
+ unless found_match
142
+ raise((msg.to_s || "Message is nil!"))
143
+ end
144
+
145
+ if dialog_creating
146
+ create_dialog msg
147
+ end
148
+ msg
149
+ end
150
+
151
+ # Receives a SIP response.
152
+ #
153
+ # Options:
154
+ # :dialog_creating - whether the dialog state (peer tags, etc.)
155
+ # should be updated with information from this response. Defaults
156
+ # to false.
157
+ # :ignore_responses - a list of status codes to ignore (e.g.
158
+ # [100] will mean that 100 Tryings are ignored rather than
159
+ # treated as unexpected).
160
+ def recv_response(code, options={})
161
+ dialog_creating = options[:dialog_creating] || false
162
+ ignore_responses = options[:ignore_responses] || []
163
+ begin
164
+ msg = recv_something
165
+ rescue Timeout::Error
166
+ raise "#{ @uri } timed out waiting for #{ code }"
167
+ end
168
+
169
+ if msg.type != :response
170
+ raise "Expected #{code}, got #{msg}"
171
+ elsif ignore_responses.include? msg.status_code
172
+ return recv_response(code, options)
173
+ else
174
+ unless Regexp.new(code) =~ msg.status_code
175
+ raise "Expected #{code}, got #{msg.status_code}"
176
+ end
177
+ end
178
+
179
+ if dialog_creating
180
+ create_dialog_from_response msg
181
+ end
182
+
183
+ msg
184
+ end
185
+
186
+ # Sends a SIP response with the given status code and reason phrase.
187
+ #
188
+ # Options:
189
+ # :body - the SIP body to use.
190
+ # :sdp_body - as :body, but an appropriate Content-Type header is
191
+ # automatically added.
192
+ # :response_to - a message to use the branch and CSeq from.
193
+ # Useful for responding to an INVITE after handling a CANCEL
194
+ # transaction.
195
+ # :retrans - whether or not to retransmit this periodically until
196
+ # the next message is received. Defaults to false.
197
+ # :headers - a map of headers to use in this message
198
+
199
+ def send_response(code, phrase, options={})
200
+ body = options[:body] || ""
201
+ retrans = options[:retrans] || false
202
+ headers = options[:headers] || {}
203
+ if options[:sdp_body]
204
+ body = options[:sdp_body]
205
+ headers['Content-Type'] = "application/sdp"
206
+ end
207
+
208
+ if options[:response_to]
209
+ assoc_with_msg(options[:response_to])
210
+ headers['CSeq'] ||= CSeq.new(options[:response_to].header("CSeq"))
211
+ end
212
+
213
+ method = nil
214
+ msg = build_message headers, body, :response, method, code, phrase
215
+ send_something(msg, retrans)
216
+ end
217
+
218
+ # Sends a SIP request with the given method.
219
+ #
220
+ # Options:
221
+ # :body - the SIP body to use.
222
+ # :sdp_body - as :body, but an appropriate Content-Type header is
223
+ # automatically added.
224
+ # :new_tsx - whether to generate a new branch ID. Defaults to true.
225
+ # :same_tsx_as - a message to use the branch and CSeq from.
226
+ # Useful for ACKing to an INVITE after handling a PRACK
227
+ # transaction.
228
+ # :retrans - whether or not to retransmit this periodically until
229
+ # the next message is received. Defaults to true unless the
230
+ # method is ACK.
231
+ # :headers - a map of headers to use in this message
232
+ def send_request(method, options={})
233
+ body = options[:body] || ""
234
+ headers = options[:headers] || {}
235
+ new_tsx = options[:new_tsx].nil? ? true : options[:new_tsx]
236
+ retrans =
237
+ if options[:retrans].nil?
238
+ if method == "ACK"
239
+ false
240
+ else
241
+ true
242
+ end
243
+ else
244
+ options[:retrans]
245
+ end
246
+
247
+ if options[:sdp_body]
248
+ body = options[:sdp_body]
249
+ headers['Content-Type'] = "application/sdp"
250
+ end
251
+
252
+ if options[:same_tsx_as]
253
+ assoc_with_msg(options[:same_tsx_as])
254
+ end
255
+
256
+ if new_tsx
257
+ update_branch
258
+ end
259
+
260
+ msg = build_message headers, body, :request, method
261
+ send_something(msg, retrans)
262
+ end
263
+
264
+ def end_call
265
+ @cxn.mark_call_dead @dialog.call_id
266
+ end
267
+
268
+ def get_next_hop header
269
+ /<sip:(.+@)?(.+):(\d+);(.*)>/ =~ header
270
+ sock = TCPSocket.new $2, $3
271
+ return TCPSource.new sock
272
+ end
273
+
274
+ private
275
+ def assoc_with_msg(msg)
276
+ @last_Via = msg.all_headers("Via")
277
+ end
278
+
279
+ # Changes the branch parameter if the Via header, creating a new transaction
280
+ def update_branch via_hdr=nil
281
+ via_hdr ||= get_new_via_hdr
282
+ @last_Via = via_hdr
283
+ end
284
+
285
+ alias_method :new_transaction, :update_branch
286
+
287
+ def get_new_via_hdr
288
+ "SIP/2.0/#{@cxn.transport} #{Quaff::Utils.local_ip}:#{@cxn.local_port};rport;branch=#{Quaff::Utils::new_branch}"
289
+ end
290
+
291
+ def recv_something
292
+ msg = @cxn.get_new_message @dialog.call_id
293
+ @retrans_keys.delete @current_retrans
294
+ @src = msg.source
295
+ @last_Via = msg.headers["Via"]
296
+ @last_CSeq = CSeq.new(msg.header("CSeq"))
297
+ msg
298
+ end
299
+
300
+ def calculate_cseq type, method
301
+ if (type == :response)
302
+ @last_CSeq.to_s
303
+ else
304
+ if (method != "ACK") and (method != "CANCEL")
305
+ @dialog.cseq += 1
306
+ end
307
+ "#{@dialog.cseq} #{method}"
308
+ end
309
+ end
310
+
311
+ def build_message headers, body, type, method=nil, code=nil, phrase=nil
312
+ is_request = code.nil?
313
+
314
+ defaults = {
315
+ "Call-ID" => @dialog.call_id,
316
+ "CSeq" => calculate_cseq(type, method),
317
+ "Via" => @last_Via,
318
+ "Max-Forwards" => "70",
319
+ "Content-Length" => "0",
320
+ "User-Agent" => "Quaff SIP Scripting Engine",
321
+ "Contact" => @cxn.contact_header
322
+ }
323
+
324
+ if is_request
325
+ defaults['From'] = @dialog.local_fromto
326
+ defaults['To'] = @dialog.peer_fromto
327
+ defaults['Route'] = @dialog.routeset
328
+ else
329
+ defaults['To'] = @dialog.local_fromto
330
+ defaults['From'] = @dialog.peer_fromto
331
+ defaults['Record-Route'] = @dialog.routeset
332
+ end
333
+
334
+ defaults.merge! headers
335
+
336
+ SipMessage.new(method, code, phrase, @dialog.target, body, defaults.merge!(headers)).to_s
337
+ end
338
+
339
+ def send_something(msg, retrans)
340
+ @cxn.send_msg(msg, @src)
341
+ if retrans and (@cxn.transport == "UDP") then
342
+ key = SecureRandom::hex
343
+ @current_retrans = key
344
+ @retrans_keys[key] = true
345
+ Thread.new do
346
+ timer = @t1
347
+ sleep timer
348
+ while @retrans_keys[key] do
349
+ #puts "Retransmitting #{ msg } on call #{ @dialog.call_id }"
350
+ @cxn.send_msg(msg, @src)
351
+ timer *=2
352
+ if timer > @t2 then
353
+ raise "Too many retransmits!"
354
+ end
355
+ sleep timer
356
+ end
357
+ end
358
+ end
359
+ end
360
+
361
+ def create_dialog_from_request msg
362
+ @dialog.established = true
363
+
364
+ set_dialog_target msg.first_header("Contact")
365
+
366
+ unless msg.all_headers("Record-Route").nil?
367
+ @dialog.routeset = msg.all_headers("Record-Route")
368
+ end
369
+
370
+ @dialog.get_peer_info msg.header("From")
371
+ end
372
+
373
+ def create_dialog_from_response msg
374
+ @dialog.established = true
375
+
376
+ set_dialog_target msg.first_header("Contact")
377
+
378
+ unless msg.all_headers("Record-Route").nil?
379
+ @dialog.routeset = msg.all_headers("Record-Route").reverse
380
+ end
381
+
382
+ @dialog.get_peer_info msg.header("To")
383
+ end
384
+ end
385
+ end
@@ -14,6 +14,58 @@ module Quaff
14
14
  attr_accessor :msg_trace, :uri, :sdp_port, :sdp_socket, :local_hostname
15
15
  attr_reader :msg_log, :local_port, :instance_id
16
16
 
17
+ # Constructs a new endpoint
18
+ # Params:
19
+ # +uri+:: The SIP URI of this endpoint
20
+ # +username+:: The authentication username of this endpoint
21
+ # +password+:: The authentication password of this endpoint
22
+ # +local_port+:: The port this endpoint should bind to. Use
23
+ # ':anyport' to bind to an ephemeral port.
24
+ # +outbound_proxy+:: The outbound proxy where all requests should
25
+ # be directed. Optional, but it only makes sense to omit it when
26
+ # Quaff is emulating a server rather than a client.
27
+ # +outbound_port+:: The port of the outbound proxy
28
+ def initialize(uri, username, password, local_port, outbound_proxy=nil, outbound_port=5060)
29
+ @msg_log = Array.new
30
+ @uri = uri
31
+ @resolver = Resolv::DNS.new
32
+ @username = username
33
+ @password = password
34
+ @local_hostname = Utils::local_ip
35
+ @local_port = local_port
36
+ initialize_connection
37
+ if outbound_proxy
38
+ @outbound_connection = new_connection(outbound_proxy, outbound_port)
39
+ end
40
+ @hashes = []
41
+ @contact_params = {}
42
+ @contact_uri_params = {"transport" => transport, "ob" => true}
43
+ @terminated = false
44
+ @last_sent_msg = nil
45
+ initialize_queues
46
+ start
47
+ end
48
+
49
+ # Retrieves the next unhandled call for this endpoint and returns
50
+ # a +Call+ object representing it
51
+ def incoming_call
52
+ begin
53
+ call_id = get_new_call_id
54
+ rescue Timeout::Error
55
+ raise "#{ @uri } timed out waiting for new incoming call"
56
+ end
57
+
58
+ puts "Call-Id for endpoint on #{@local_port} is #{call_id}" if @msg_trace
59
+ Call.new(self, call_id, @uri, nil)
60
+ end
61
+
62
+ # Creates a +Call+ object representing a new outbound call
63
+ def outgoing_call to_uri
64
+ call_id = generate_call_id
65
+ puts "Call-Id for endpoint on #{@local_port} is #{call_id}" if @msg_trace
66
+ Call.new(self, call_id, @uri, to_uri, @outbound_connection, @instance_id)
67
+ end
68
+
17
69
  # Creates an SDP socket bound to an ephemeral port
18
70
  def setup_sdp
19
71
  @sdp_socket = UDPSocket.new
@@ -56,26 +108,6 @@ module Quaff
56
108
  add_contact_param "+sip.instance", "\"<urn:uuid:#{id}>\""
57
109
  end
58
110
 
59
- # Retrieves the next unhandled call for this endpoint and returns
60
- # a +Call+ object representing it
61
- def incoming_call
62
- begin
63
- call_id = get_new_call_id
64
- rescue Timeout::Error
65
- raise "#{ @uri } timed out waiting for new incoming call"
66
- end
67
-
68
- puts "Call-Id for endpoint on #{@local_port} is #{call_id}" if @msg_trace
69
- Call.new(self, call_id, @instance_id, @uri)
70
- end
71
-
72
- # Creates a +Call+ object representing a new outbound call
73
- def outgoing_call to_uri
74
- call_id = generate_call_id
75
- puts "Call-Id for endpoint on #{@local_port} is #{call_id}" if @msg_trace
76
- Call.new(self, call_id, @instance_id, @uri, @outbound_connection, to_uri)
77
- end
78
-
79
111
  # Not yet ready for use
80
112
  def create_client(uri, username, password, outbound_proxy, outbound_port=5060) # :nodoc:
81
113
  end
@@ -88,38 +120,6 @@ module Quaff
88
120
  def create_aka_client(uri, username, key, op, outbound_proxy, outbound_port=5060) # :nodoc:
89
121
  end
90
122
 
91
- # Constructs a new endpoint
92
- # Params:
93
- # +uri+:: The SIP URI of this endpoint
94
- # +username+:: The authentication username of this endpoint
95
- # +password+:: The authentication password of this endpoint
96
- # +local_port+:: The port this endpoint should bind to. Use
97
- # ':anyport' to bind to an ephemeral port.
98
- # +outbound_proxy+:: The outbound proxy where all requests should
99
- # be directed. Optional, but it only makes sense to omit it when
100
- # Quaff is emulating a server rather than a client.
101
- # +outbound_port+:: The port of the outbound proxy
102
- def initialize(uri, username, password, local_port, outbound_proxy=nil, outbound_port=5060)
103
- @msg_log = Array.new
104
- @uri = uri
105
- @resolver = Resolv::DNS.new
106
- @username = username
107
- @password = password
108
- @local_host = Utils::local_ip
109
- @local_port = local_port
110
- initialize_connection
111
- if outbound_proxy
112
- @outbound_connection = new_connection(outbound_proxy, outbound_port)
113
- end
114
- @hashes = []
115
- @contact_params = {}
116
- @contact_uri_params = {"transport" => transport, "ob" => true}
117
- @terminated = false
118
- @local_hostname = Utils::local_ip
119
- initialize_queues
120
- start
121
- end
122
-
123
123
  def contact_header
124
124
  param_str = Utils.paramhash_to_str(@contact_params)
125
125
  uri_param_str = Utils.paramhash_to_str(@contact_uri_params)
@@ -129,7 +129,8 @@ module Quaff
129
129
  def send_msg(data, source) # :nodoc:
130
130
  @msg_log.push "Endpoint on #{@local_port} sending:\n\n#{data.strip}\n\nto #{source.inspect}"
131
131
  puts "Endpoint on #{@local_port} sending #{data} to #{source.inspect}" if @msg_trace
132
- source.send_msg(@cxn, data)
132
+ source.send_msg(@cxn, data)
133
+ @last_sent_msg = data
133
134
  end
134
135
 
135
136
  # Not yet ready for use
@@ -150,8 +151,8 @@ module Quaff
150
151
  @reg_call ||= outgoing_call(@uri)
151
152
  auth_hdr = Quaff::Auth.gen_empty_auth_header @username
152
153
  @reg_call.update_branch
153
- @reg_call.send_request("REGISTER", "", {"Authorization" => auth_hdr, "Expires" => expires.to_s})
154
- response_data = @reg_call.recv_response("401|200")
154
+ @reg_call.send_request("REGISTER", retrans: true, headers: {"Authorization" => auth_hdr, "Expires" => expires.to_s})
155
+ response_data = @reg_call.recv_response_and_create_dialog("401|200")
155
156
  if response_data.status_code == "401"
156
157
  if aka
157
158
  rand = Quaff::Auth.extract_rand response_data.header("WWW-Authenticate")
@@ -161,7 +162,7 @@ module Quaff
161
162
  end
162
163
  auth_hdr = Quaff::Auth.gen_auth_header response_data.header("WWW-Authenticate"), @username, @password, "REGISTER", @uri
163
164
  @reg_call.update_branch
164
- @reg_call.send_request("REGISTER", "", {"Authorization" => auth_hdr, "Expires" => expires.to_s})
165
+ @reg_call.send_request("REGISTER", retrans: true, headers: {"Authorization" => auth_hdr, "Expires" => expires.to_s})
165
166
  response_data = @reg_call.recv_response("200")
166
167
  end
167
168
  return response_data # always the 200 OK
@@ -234,6 +235,7 @@ module Quaff
234
235
  if is_retransmission? msg
235
236
  @msg_log.push "Endpoint on #{@local_port} received retransmission"
236
237
  puts "Endpoint on #{@local_port} received retransmission" if @msg_trace
238
+ source.send_msg(@cxn, @last_sent_msg) if @last_sent_msg
237
239
  return
238
240
  end
239
241
 
File without changes
@@ -0,0 +1,43 @@
1
+ # -*- coding: us-ascii -*-
2
+ require 'securerandom'
3
+ require_relative './sip_parser.rb'
4
+
5
+ module Quaff
6
+ class SipDialog
7
+ attr_accessor :local_uri, :local_tag, :peer_uri, :peer_tag, :target, :call_id, :cseq, :routeset, :established
8
+
9
+ def initialize call_id, local_uri, peer_uri
10
+ @cseq = 1
11
+ @call_id = call_id
12
+ @established = false
13
+ @routeset = []
14
+ @local_tag = SecureRandom::hex
15
+ @peer_uri = peer_uri
16
+ @target = peer_uri
17
+ @local_uri = local_uri
18
+ end
19
+
20
+ def get_peer_info fromto_hdr
21
+ tospec = ToSpec.new
22
+ tospec.parse(fromto_hdr)
23
+ @peer_tag = tospec.params['tag']
24
+ @peer_uri = tospec.uri
25
+ end
26
+
27
+ def local_fromto
28
+ if @local_tag
29
+ "<#{@local_uri}>;tag=#{@local_tag}"
30
+ else
31
+ "<#{@local_uri}>"
32
+ end
33
+ end
34
+
35
+ def peer_fromto
36
+ if @peer_tag
37
+ "<#{@peer_uri}>;tag=#{@peer_tag}"
38
+ else
39
+ "<#{@peer_uri}>"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -35,7 +35,7 @@ module Quaff
35
35
  elsif udp and msg.header("Content-Length").nil?
36
36
  add_body io.read(1500)
37
37
  end
38
- @msg
38
+ msg
39
39
  end
40
40
 
41
41
  def parse_start
@@ -195,13 +195,8 @@ module Quaff
195
195
  uri_parameters)
196
196
  end
197
197
 
198
- def tel_uri
199
- Concat.new(Literal.new("tel:"),
200
- Repetition.new([:at_least, 1], Digit.new))
201
- end
202
-
203
198
  def addr_spec
204
- Alternate.new(sip_uri, tel_uri)
199
+ sip_uri
205
200
  end
206
201
 
207
202
  def wsp
File without changes
@@ -19,10 +19,6 @@ def Utils.pid
19
19
  Process.pid
20
20
  end
21
21
 
22
- def Utils.new_call_id
23
- "#{pid}_#{Time.new.to_i}@#{local_ipv4}"
24
- end
25
-
26
22
  def Utils.new_branch
27
23
  "z9hG4bK#{Time.new.to_f}"
28
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quaff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.8.0pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Day
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-20 00:00:00.000000000 Z
11
+ date: 2015-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: system-getifaddrs
@@ -58,14 +58,15 @@ executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
60
60
  files:
61
- - lib/auth.rb
62
- - lib/call.rb
63
- - lib/endpoint.rb
64
- - lib/message.rb
65
61
  - lib/quaff.rb
66
- - lib/sip_parser.rb
67
- - lib/sources.rb
68
- - lib/utils.rb
62
+ - lib/quaff/auth.rb
63
+ - lib/quaff/call.rb
64
+ - lib/quaff/endpoint.rb
65
+ - lib/quaff/message.rb
66
+ - lib/quaff/sip_dialog.rb
67
+ - lib/quaff/sip_parser.rb
68
+ - lib/quaff/sources.rb
69
+ - lib/quaff/utils.rb
69
70
  homepage: http://github.com/rkday/quaff
70
71
  licenses:
71
72
  - GPL3
@@ -82,14 +83,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
83
  version: '0'
83
84
  required_rubygems_version: !ruby/object:Gem::Requirement
84
85
  requirements:
85
- - - ">="
86
+ - - ">"
86
87
  - !ruby/object:Gem::Version
87
- version: '0'
88
+ version: 1.3.1
88
89
  requirements: []
89
90
  rubyforge_project:
90
- rubygems_version: 2.4.8
91
+ rubygems_version: 2.4.3
91
92
  signing_key:
92
93
  specification_version: 4
93
94
  summary: Quaff
94
95
  test_files: []
95
- has_rdoc:
data/lib/call.rb DELETED
@@ -1,339 +0,0 @@
1
- # -*- coding: us-ascii -*-
2
- require 'securerandom'
3
- require 'timeout'
4
- require_relative './utils.rb'
5
- require_relative './sources.rb'
6
- require_relative './auth.rb'
7
- require_relative './message.rb'
8
-
9
- module Quaff
10
- class CSeq # :nodoc:
11
- attr_reader :num
12
- def initialize cseq_str
13
- @num, @method = cseq_str.split
14
- @num = @num.to_i
15
- end
16
-
17
- def increment
18
- @num = @num + 1
19
- to_s
20
- end
21
-
22
- def to_s
23
- "#{@num.to_s} #{@method}"
24
- end
25
- end
26
-
27
- class Call
28
- attr_reader :cid, :last_To, :last_From, :sip_destination
29
-
30
- def initialize(cxn,
31
- cid,
32
- instance_id=nil,
33
- uri=nil,
34
- destination=nil,
35
- target_uri=nil)
36
- @cxn = cxn
37
- setdest(destination, recv_from_this: true) if destination
38
- @retrans = nil
39
- @t1, @t2 = 0.5, 32
40
- @instance_id = instance_id
41
- @cid = cid
42
- set_default_headers cid, uri, target_uri
43
- end
44
-
45
- # Changes the branch parameter if the Via header, creating a new transaction
46
- def update_branch via_hdr=nil
47
- via_hdr ||= get_new_via_hdr
48
- @last_Via = via_hdr
49
- end
50
-
51
- alias_method :new_transaction, :update_branch
52
-
53
- def get_new_via_hdr
54
- "SIP/2.0/#{@cxn.transport} #{Quaff::Utils.local_ip}:#{@cxn.local_port};rport;branch=#{Quaff::Utils::new_branch}"
55
- end
56
-
57
- def set_callee uri
58
- if /<(.*?)>/ =~ uri
59
- uri = $1
60
- end
61
-
62
- @sip_destination = "#{uri}"
63
- end
64
-
65
- def create_dialog msg
66
- if @in_dialog
67
- return
68
- end
69
-
70
- @in_dialog = true
71
-
72
- uri = msg.first_header("Contact")
73
-
74
- if /<(.*?)>/ =~ uri
75
- uri = $1
76
- end
77
-
78
- @sip_destination = uri
79
-
80
- unless msg.all_headers("Record-Route").nil?
81
- if msg.type == :request
82
- @routeset = msg.all_headers("Record-Route")
83
- else
84
- @routeset = msg.all_headers("Record-Route").reverse
85
- end
86
- end
87
-
88
- end
89
-
90
- # Sets the Source where messages in this call should be sent to by
91
- # default.
92
- #
93
- # Options:
94
- # :recv_from_this - if true, also listens for any incoming
95
- # messages over this source's connection. (This is only
96
- # meaningful for connection-oriented transports.)
97
- def setdest source, options={}
98
- @src = source
99
- if options[:recv_from_this] and source.sock
100
- @cxn.add_sock source.sock
101
- end
102
- end
103
-
104
- def recv_request(method, dialog_creating=true)
105
- begin
106
- msg = recv_something
107
- rescue Timeout::Error
108
- raise "#{ @uri } timed out waiting for #{ method }"
109
- end
110
-
111
- unless msg.type == :request \
112
- and Regexp.new(method) =~ msg.method
113
- raise((msg.to_s || "Message is nil!"))
114
- end
115
-
116
- unless @has_To_tag
117
- @has_To_tag = true
118
- tospec = ToSpec.new
119
- tospec.parse(msg.header("To"))
120
- tospec.params['tag'] = generate_random_tag
121
- @last_To = tospec.to_s
122
- @last_From = msg.header("From")
123
- end
124
-
125
- if dialog_creating
126
- create_dialog msg
127
- end
128
- msg
129
- end
130
-
131
- # Waits until the next message comes in, and handles it if it is one
132
- # of possible_messages.
133
- #
134
- # possible_messages is a list of things that can be received.
135
- # Elements can be:
136
- # * a string representing the SIP method, e.g. "INVITE"
137
- # * a number representing the SIP status code, e.g. 200
138
- # * a two-item list, containing one of the above and a boolean
139
- # value, which indicates whether this message is dialog-creating. by
140
- # default, requests are assumed to be dialog-creating and responses
141
- # are not.
142
- #
143
- # For example, ["INVITE", 301, ["ACK", false], [200, true]] is a
144
- # valid value for possible_messages.
145
- def recv_any_of(possible_messages)
146
- begin
147
- msg = recv_something
148
- rescue Timeout::Error
149
- raise "#{ @uri } timed out waiting for one of these: #{possible_messages}"
150
- end
151
-
152
- found_match = false
153
- dialog_creating = nil
154
-
155
- possible_messages.each do
156
- | what, this_dialog_creating |
157
- type = if (what.class == String) then :request else :response end
158
- if this_dialog_creating.nil?
159
- this_dialog_creating = (type == :request)
160
- end
161
-
162
- found_match =
163
- if type == :request
164
- msg.type == :request and what == msg.method
165
- else
166
- msg.type == :response and what.to_s == msg.status_code
167
- end
168
-
169
- if found_match
170
- dialog_creating = this_dialog_creating
171
- break
172
- end
173
- end
174
-
175
- unless found_match
176
- raise((msg.to_s || "Message is nil!"))
177
- end
178
-
179
- if msg.type == :request
180
- unless @has_To_tag
181
- @has_To_tag = true
182
- tospec = ToSpec.new
183
- tospec.parse(msg.header("To"))
184
- tospec.params['tag'] = generate_random_tag
185
- @last_To = tospec.to_s
186
- @last_From = msg.header("From")
187
- end
188
- else
189
- if @in_dialog
190
- @has_To_tag = true
191
- @last_To = msg.header("To")
192
- end
193
- end
194
-
195
- if dialog_creating
196
- create_dialog msg
197
- end
198
- msg
199
- end
200
-
201
- def recv_response(code, dialog_creating=false)
202
- begin
203
- msg = recv_something
204
- rescue Timeout::Error
205
- raise "#{ @uri } timed out waiting for #{ code }"
206
- end
207
- unless msg.type == :response \
208
- and Regexp.new(code) =~ msg.status_code
209
- raise "Expected #{ code}, got #{msg.status_code || msg}"
210
- end
211
-
212
- if dialog_creating
213
- create_dialog msg
214
- end
215
-
216
- if @in_dialog
217
- @has_To_tag = true
218
- @last_To = msg.header("To")
219
- end
220
-
221
- msg
222
- end
223
-
224
- def recv_response_and_create_dialog(code)
225
- recv_response code, true
226
- end
227
-
228
- def send_response(code, phrase, body="", retrans=nil, headers={})
229
- method = nil
230
- msg = build_message headers, body, :response, method, code, phrase
231
- send_something(msg, retrans)
232
- end
233
-
234
- def send_request(method, body="", headers={})
235
- msg = build_message headers, body, :request, method
236
- send_something(msg, nil)
237
- end
238
-
239
- def end_call
240
- @cxn.mark_call_dead @cid
241
- end
242
-
243
- def assoc_with_msg(msg)
244
- @last_Via = msg.all_headers("Via")
245
- @last_CSeq = CSeq.new(msg.header("CSeq"))
246
- end
247
-
248
- def get_next_hop header
249
- /<sip:(.+@)?(.+):(\d+);(.*)>/ =~ header
250
- sock = TCPSocket.new $2, $3
251
- return TCPSource.new sock
252
- end
253
-
254
- private
255
- def recv_something
256
- msg = @cxn.get_new_message @cid
257
- @retrans = nil
258
- @src = msg.source
259
- set_callee msg.header("From")
260
- @last_Via = msg.headers["Via"]
261
- @last_CSeq = CSeq.new(msg.header("CSeq"))
262
- msg
263
- end
264
-
265
- def calculate_cseq type, method
266
- if (type == :response)
267
- @last_CSeq.to_s
268
- elsif (method == "ACK")
269
- "#{@last_CSeq.num} ACK"
270
- else
271
- @cseq_number = @cseq_number + 1
272
- "#{@cseq_number} #{method}"
273
- end
274
- end
275
-
276
- def build_message headers, body, type, method=nil, code=nil, phrase=nil
277
- defaults = {
278
- "From" => @last_From,
279
- "To" => @last_To,
280
- "Call-ID" => @cid,
281
- "CSeq" => calculate_cseq(type, method),
282
- "Via" => @last_Via,
283
- "Max-Forwards" => "70",
284
- "Content-Length" => "0",
285
- "User-Agent" => "Quaff SIP Scripting Engine",
286
- "Contact" => @cxn.contact_header
287
- }
288
-
289
- is_request = code.nil?
290
- if is_request
291
- defaults['Route'] = @routeset
292
- else
293
- defaults['Record-Route'] = @routeset
294
- end
295
-
296
- defaults.merge! headers
297
-
298
- SipMessage.new(method, code, phrase, @sip_destination, body, defaults.merge!(headers)).to_s
299
-
300
- end
301
-
302
- def send_something(msg, retrans)
303
- @cxn.send_msg(msg, @src)
304
- if retrans and (@transport == "UDP") then
305
- @retrans = true
306
- Thread.new do
307
- timer = @t1
308
- sleep timer
309
- while @retrans do
310
- #puts "Retransmitting on call #{ @cid }"
311
- @cxn.send(msg, @src)
312
- timer *=2
313
- if timer < @t2 then
314
- raise "Too many retransmits!"
315
- end
316
- sleep timer
317
- end
318
- end
319
- end
320
- end
321
-
322
- def set_default_headers cid, uri, target_uri
323
- @cseq_number = 1
324
- @uri = uri
325
- @last_From = "<#{uri}>;tag=" + generate_random_tag
326
- @in_dialog = false
327
- @has_To_tag = false
328
- update_branch
329
- @last_To = "<#{target_uri}>"
330
- @sip_destination = target_uri
331
- @routeset = []
332
- end
333
-
334
- def generate_random_tag
335
- SecureRandom::hex
336
- end
337
-
338
- end
339
- end