quaff 0.3.4 → 0.4.0

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.
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: []