quaff 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/lib/auth.rb +13 -0
  2. data/lib/call.rb +57 -41
  3. data/lib/endpoint.rb +64 -27
  4. data/lib/message.rb +9 -1
  5. metadata +18 -2
@@ -1,3 +1,5 @@
1
+ require 'base64'
2
+
1
3
  module Quaff
2
4
  module Auth
3
5
  def Auth.gen_nonce auth_pairs, username, passwd, method, sip_uri
@@ -9,6 +11,17 @@ module Quaff
9
11
  return digest
10
12
  end
11
13
 
14
+ def Auth.extract_rand auth_line
15
+ # Split auth line on commas
16
+ auth_pairs = {}
17
+ auth_line.sub("Digest ", "").split(",") .each do |pair|
18
+ key, value = pair.split "="
19
+ auth_pairs[key.gsub(" ", "")] = value.gsub("\"", "").gsub(" ", "")
20
+ end
21
+ # First 128 bits are the RAND
22
+ return Base64.decode64(auth_pairs["nonce"])[0..15]
23
+ end
24
+
12
25
  def Auth.gen_auth_header auth_line, username, passwd, method, sip_uri
13
26
  # Split auth line on commas
14
27
  auth_pairs = {}
@@ -5,21 +5,21 @@ require_relative './auth.rb'
5
5
  require_relative './message.rb'
6
6
 
7
7
  module Quaff
8
- class CSeq # :nodoc:
9
- def initialize cseq_str
10
- @num, @method = cseq_str.split
11
- @num = @num.to_i
12
- end
13
-
14
- def increment
15
- @num = @num + 1
16
- to_s
17
- end
8
+ class CSeq # :nodoc:
9
+ attr_reader :num
10
+ def initialize cseq_str
11
+ @num, @method = cseq_str.split
12
+ @num = @num.to_i
13
+ end
18
14
 
19
- def to_s
20
- "#{@num.to_s} #{@method}"
21
- end
15
+ def increment
16
+ @num = @num + 1
17
+ to_s
18
+ end
22
19
 
20
+ def to_s
21
+ "#{@num.to_s} #{@method}"
22
+ end
23
23
  end
24
24
 
25
25
  class Call
@@ -49,8 +49,15 @@ class Call
49
49
  @cxn.add_call_id @cid
50
50
  end
51
51
 
52
- def update_branch
53
- @last_Via = "SIP/2.0/#{@cxn.transport} #{Quaff::Utils.local_ip}:#{@cxn.local_port};rport;branch=#{Quaff::Utils::new_branch}"
52
+ def update_branch via_hdr=nil
53
+ via_hdr ||= get_new_via_hdr
54
+ @last_Via = via_hdr
55
+ end
56
+
57
+ alias_method :new_transaction, :update_branch
58
+
59
+ def get_new_via_hdr
60
+ "SIP/2.0/#{@cxn.transport} #{Quaff::Utils.local_ip}:#{@cxn.local_port};rport;branch=#{Quaff::Utils::new_branch}"
54
61
  end
55
62
 
56
63
  def create_dialog msg
@@ -72,36 +79,46 @@ class Call
72
79
  end
73
80
  end
74
81
 
75
- def recv_request(method)
82
+ def recv_request(method, dialog_creating=true)
76
83
  begin
77
- data = recv_something
84
+ msg = recv_something
78
85
  rescue
79
86
  raise "#{ @uri } timed out waiting for #{ method }"
80
87
  end
81
- unless data["message"].type == :request \
82
- and Regexp.new(method) =~ data["message"].method
83
- raise((data['message'].to_s || "Message is nil!"))
88
+ unless msg.type == :request \
89
+ and Regexp.new(method) =~ msg.method
90
+ raise((msg.to_s || "Message is nil!"))
84
91
  end
85
- unless data['message'].all_headers("Record-Route").nil?
86
- @routeset = data['message'].all_headers("Record-Route")
92
+ if dialog_creating
93
+ set_callee msg.first_header("Contact")
94
+ unless msg.all_headers("Record-Route").nil?
95
+ @routeset = msg.all_headers("Record-Route")
96
+ end
87
97
  end
88
- data
98
+ msg
89
99
  end
90
100
 
91
- def recv_response(code)
101
+ def recv_response(code, dialog_creating=false)
92
102
  begin
93
- data = recv_something
103
+ msg = recv_something
94
104
  rescue
95
105
  raise "#{ @uri } timed out waiting for #{ code }"
96
106
  end
97
- unless data["message"].type == :response \
98
- and Regexp.new(code) =~ data["message"].status_code
99
- raise "Expected #{ code}, got #{data["message"].status_code || data['message']}"
107
+ unless msg.type == :response \
108
+ and Regexp.new(code) =~ msg.status_code
109
+ raise "Expected #{ code}, got #{msg.status_code || msg}"
100
110
  end
101
- unless data['message'].all_headers("Record-Route").nil?
102
- @routeset = data['message'].all_headers("Record-Route").reverse
111
+ if dialog_creating
112
+ set_callee msg.first_header("Contact")
113
+ unless msg.all_headers("Record-Route").nil?
114
+ @routeset = msg.all_headers("Record-Route").reverse
115
+ end
103
116
  end
104
- data
117
+ msg
118
+ end
119
+
120
+ def recv_response_and_create_dialog(code)
121
+ recv_response code, true
105
122
  end
106
123
 
107
124
  def send_response(code, phrase, body="", retrans=nil, headers={})
@@ -119,7 +136,6 @@ class Call
119
136
  @cxn.mark_call_dead @cid
120
137
  end
121
138
 
122
-
123
139
  def clear_tag str
124
140
  str
125
141
  end
@@ -143,22 +159,22 @@ class Call
143
159
 
144
160
  private
145
161
  def recv_something
146
- data = @cxn.get_new_message @cid
162
+ msg = @cxn.get_new_message @cid
147
163
  @retrans = nil
148
- @src = data['source']
149
- @last_To = data["message"].header("To")
150
- @last_From = data["message"].header("From")
151
- set_callee data["message"].header("From")
152
- @last_Via = data["message"].headers["Via"]
153
- @last_CSeq = CSeq.new(data["message"].header("CSeq"))
154
- data
164
+ @src = msg.source
165
+ @last_To = msg.header("To")
166
+ @last_From = msg.header("From")
167
+ set_callee msg.header("From")
168
+ @last_Via = msg.headers["Via"]
169
+ @last_CSeq = CSeq.new(msg.header("CSeq"))
170
+ msg
155
171
  end
156
172
 
157
173
  def calculate_cseq type, method
158
174
  if (type == :response)
159
175
  @last_CSeq.to_s
160
176
  elsif (method == "ACK")
161
- @last_CSeq.to_s
177
+ "#{@last_CSeq.num} ACK"
162
178
  else
163
179
  @cseq_number = @cseq_number + 1
164
180
  "#{@cseq_number} #{method}"
@@ -4,12 +4,19 @@ require 'thread'
4
4
  require 'timeout'
5
5
  require 'resolv'
6
6
  require 'digest/md5'
7
+ #require 'milenage'
7
8
  require_relative './sip_parser.rb'
8
9
  require_relative './sources.rb'
9
10
 
10
11
  module Quaff
11
12
  class BaseEndpoint
12
- attr_accessor :msg_trace, :uri
13
+ attr_accessor :msg_trace, :uri, :sdp_port, :sdp_socket
14
+
15
+ def setup_sdp
16
+ @sdp_socket = UDPSocket.new
17
+ @sdp_socket.bind('0.0.0.0', 0)
18
+ @sdp_port = @sdp_socket.addr[1]
19
+ end
13
20
 
14
21
  def generate_call_id
15
22
  digest = Digest::MD5.hexdigest(rand(60000).to_s)
@@ -33,13 +40,23 @@ module Quaff
33
40
  Call.new(self, call_id, @uri, @outbound_connection, to_uri)
34
41
  end
35
42
 
43
+ def create_client(uri, username, password, outbound_proxy, outbound_port=5060)
44
+ end
45
+
46
+ def create_server(uri, local_port=5060, outbound_proxy=nil, outbound_port=5060)
47
+
48
+ end
49
+
50
+ def create_aka_client(uri, username, key, op, outbound_proxy, outbound_port=5060)
51
+ end
52
+
36
53
  def initialize(uri, username, password, local_port, outbound_proxy=nil, outbound_port=5060)
37
54
  @uri = uri
38
55
  @resolver = Resolv::DNS.new
39
56
  @username = username
40
57
  @password = password
41
58
  @lport = local_port
42
- initialize_connection @lport
59
+ initialize_connection
43
60
  if outbound_proxy
44
61
  @outbound_connection = new_connection(outbound_proxy, outbound_port)
45
62
  end
@@ -75,18 +92,28 @@ module Quaff
75
92
  source.send_msg(@cxn, data)
76
93
  end
77
94
 
78
- def register expires="3600"
95
+ def set_aka_credentials key, op
96
+ @kernel = Milenage.Kernel key
97
+ @kernel.op = op
98
+ end
99
+
100
+ def register expires="3600", aka=false
79
101
  call = outgoing_call(@uri)
80
102
  call.send_request("REGISTER", "", { "Expires" => expires.to_s })
81
103
  response_data = call.recv_response("401|200")
82
- if response_data['message'].status_code == "401"
83
- call.send_request("ACK")
84
- auth_hdr = Quaff::Auth.gen_auth_header response_data['message'].header("WWW-Authenticate"), @username, @password, "REGISTER", @uri
104
+ if response_data.status_code == "401"
105
+ if aka
106
+ rand = Quaff::Auth.extract_rand response_data.header("WWW-Authenticate")
107
+ password = @kernel.f3 rand
108
+ else
109
+ password = @password
110
+ end
111
+ auth_hdr = Quaff::Auth.gen_auth_header response_data.header("WWW-Authenticate"), @username, @password, "REGISTER", @uri
85
112
  call.update_branch
86
113
  call.send_request("REGISTER", "", {"Authorization" => auth_hdr, "Expires" => expires.to_s, "CSeq" => "2 REGISTER"})
87
- call.recv_response("200")
114
+ response_data = call.recv_response("200")
88
115
  end
89
- return true
116
+ return response_data # always the 200 OK
90
117
  end
91
118
 
92
119
  def unregister
@@ -110,27 +137,32 @@ module Quaff
110
137
  end
111
138
 
112
139
  def queue_msg(msg, source)
113
- puts "Endpoint on #{@lport} received #{msg} from #{source.inspect}" if @msg_trace
114
- cid = @parser.message_identifier msg
115
- if cid and not @dead_calls.has_key? cid then
116
- unless @messages.has_key? cid then
117
-
118
- add_call_id cid
119
- @call_ids.enq cid
120
- end
121
- @messages[cid].enq({"message" => msg, "source" => source})
140
+ puts "Endpoint on #{@lport} received #{msg} from
141
+ ##{source.inspect}" if @msg_trace
142
+ msg.source = source
143
+ cid = @parser.message_identifier msg
144
+ if cid and not @dead_calls.has_key? cid then
145
+ unless @messages.has_key? cid then
146
+ add_call_id cid
147
+ @call_ids.enq cid
122
148
  end
149
+ @messages[cid].enq(msg)
150
+ end
123
151
  end
124
-
125
152
  end
126
153
 
127
154
  class TCPSIPEndpoint < BaseEndpoint
128
155
  attr_accessor :sockets
129
156
 
130
- def initialize_connection(lport)
131
- @cxn = TCPServer.new(lport)
132
- @parser = SipParser.new
133
- @sockets = []
157
+ def initialize_connection
158
+ if @lport != :anyport
159
+ @cxn = TCPServer.new(@lport)
160
+ else
161
+ @cxn = TCPServer.new(0)
162
+ @lport = @cxn.addr[1]
163
+ end
164
+ @parser = SipParser.new
165
+ @sockets = []
134
166
  end
135
167
 
136
168
  def transport
@@ -201,11 +233,16 @@ module Quaff
201
233
  alias_method :new_connection, :new_source
202
234
 
203
235
  private
204
- def initialize_connection(lport)
205
- @cxn = UDPSocket.new
206
- @cxn.bind('0.0.0.0', lport)
207
- @sockets = []
208
- @parser = SipParser.new
236
+ def initialize_connection
237
+ @cxn = UDPSocket.new
238
+ if @lport != :anyport
239
+ @cxn.bind('0.0.0.0', @lport)
240
+ else
241
+ @cxn.bind('0.0.0.0', 0)
242
+ @lport = @cxn.addr[1]
243
+ end
244
+ @sockets = []
245
+ @parser = SipParser.new
209
246
  end
210
247
 
211
248
  def recv_msg
@@ -1,6 +1,6 @@
1
1
  module Quaff
2
2
  class SipMessage
3
- attr_accessor :method, :requri, :reason, :status_code, :headers
3
+ attr_accessor :method, :requri, :reason, :status_code, :headers, :source
4
4
  attr_reader :body
5
5
 
6
6
  def initialize method=nil, status_code=nil, reason=nil,
@@ -11,6 +11,14 @@ class SipMessage
11
11
  @headers['Content-Length'] = [body.length]
12
12
  end
13
13
 
14
+ def [] key
15
+ if key == "message"
16
+ self
17
+ elsif key == "source"
18
+ @source
19
+ end
20
+ end
21
+
14
22
  def type
15
23
  if @method
16
24
  :request
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quaff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-17 00:00:00.000000000 Z
12
+ date: 2013-12-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: facter
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: 1.7.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: milenage
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 0.1.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.1.0
30
46
  description: A Ruby library for writing SIP test scenarios
31
47
  email: rkd@rkd.me.uk
32
48
  executables: []