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.
- data/lib/auth.rb +13 -0
- data/lib/call.rb +57 -41
- data/lib/endpoint.rb +64 -27
- data/lib/message.rb +9 -1
- metadata +18 -2
data/lib/auth.rb
CHANGED
@@ -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 = {}
|
data/lib/call.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
-
|
84
|
+
msg = recv_something
|
78
85
|
rescue
|
79
86
|
raise "#{ @uri } timed out waiting for #{ method }"
|
80
87
|
end
|
81
|
-
unless
|
82
|
-
and Regexp.new(method) =~
|
83
|
-
raise((
|
88
|
+
unless msg.type == :request \
|
89
|
+
and Regexp.new(method) =~ msg.method
|
90
|
+
raise((msg.to_s || "Message is nil!"))
|
84
91
|
end
|
85
|
-
|
86
|
-
|
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
|
-
|
98
|
+
msg
|
89
99
|
end
|
90
100
|
|
91
|
-
def recv_response(code)
|
101
|
+
def recv_response(code, dialog_creating=false)
|
92
102
|
begin
|
93
|
-
|
103
|
+
msg = recv_something
|
94
104
|
rescue
|
95
105
|
raise "#{ @uri } timed out waiting for #{ code }"
|
96
106
|
end
|
97
|
-
unless
|
98
|
-
and Regexp.new(code) =~
|
99
|
-
raise "Expected #{ code}, got #{
|
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
|
-
|
102
|
-
|
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
|
-
|
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
|
-
|
162
|
+
msg = @cxn.get_new_message @cid
|
147
163
|
@retrans = nil
|
148
|
-
@src =
|
149
|
-
@last_To =
|
150
|
-
@last_From =
|
151
|
-
set_callee
|
152
|
-
@last_Via =
|
153
|
-
@last_CSeq = CSeq.new(
|
154
|
-
|
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.
|
177
|
+
"#{@last_CSeq.num} ACK"
|
162
178
|
else
|
163
179
|
@cseq_number = @cseq_number + 1
|
164
180
|
"#{@cseq_number} #{method}"
|
data/lib/endpoint.rb
CHANGED
@@ -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
|
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
|
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
|
83
|
-
|
84
|
-
|
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
|
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
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
131
|
-
|
132
|
-
@
|
133
|
-
|
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
|
205
|
-
|
206
|
-
|
207
|
-
@
|
208
|
-
|
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
|
data/lib/message.rb
CHANGED
@@ -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.
|
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-
|
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: []
|