quaff 0.7.3 → 0.8.0pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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