quaff 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/call.rb +230 -0
- data/lib/endpoint.rb +175 -0
- data/lib/quaff.rb +2 -0
- data/lib/sip_parser.rb +133 -0
- data/lib/sources.rb +60 -0
- data/lib/utils.rb +23 -0
- metadata +51 -0
data/lib/call.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
# -*- coding: us-ascii -*-
|
2
|
+
require_relative './utils.rb'
|
3
|
+
require_relative './sources.rb'
|
4
|
+
|
5
|
+
class CSeq
|
6
|
+
def initialize cseq_str
|
7
|
+
@num, @method = cseq_str.split
|
8
|
+
@num = @num.to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def increment
|
12
|
+
@num = @num +1
|
13
|
+
to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
"#{@num.to_s} #{@method}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class Call
|
23
|
+
attr_reader :cid
|
24
|
+
|
25
|
+
def initialize(cxn,
|
26
|
+
cid,
|
27
|
+
uri="sip:5557777888@#{QuaffUtils.local_ip}",
|
28
|
+
destination=nil,
|
29
|
+
target_uri=nil)
|
30
|
+
@cxn = cxn
|
31
|
+
change_cid cid
|
32
|
+
@uri = uri
|
33
|
+
@retrans = nil
|
34
|
+
@t1, @t2 = 0.5, 32
|
35
|
+
@last_From = "<#{uri}>"
|
36
|
+
update_branch
|
37
|
+
setdest(destination, recv_from_this: true) if destination
|
38
|
+
set_callee target_uri if target_uri
|
39
|
+
@routeset = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def change_cid cid
|
43
|
+
@cid = cid
|
44
|
+
@cxn.add_call_id @cid
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_branch
|
48
|
+
@last_Via = "SIP/2.0/#{@cxn.transport} #{QuaffUtils.local_ip}:#{@cxn.local_port};rport;branch=#{QuaffUtils.new_branch}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_dialog msg
|
52
|
+
set_callee msg.first_header("Contact")
|
53
|
+
@routeset = msg.all_headers("Record-Route")
|
54
|
+
if msg.type == :request
|
55
|
+
@routeset = @routeset.reverse
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def recv_something
|
60
|
+
data = @cxn.get_new_message @cid
|
61
|
+
@retrans = nil
|
62
|
+
@src = data['source']
|
63
|
+
@last_To = data["message"].header("To")
|
64
|
+
@last_From = data["message"].header("From")
|
65
|
+
@sip_destination ||= data["message"].header("From")
|
66
|
+
@last_Via = data["message"].headers["Via"]
|
67
|
+
@last_CSeq = CSeq.new(data["message"].header("CSeq"))
|
68
|
+
data
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_callee uri
|
72
|
+
if /<(.*)>/ =~ uri
|
73
|
+
uri = $1
|
74
|
+
end
|
75
|
+
|
76
|
+
@sip_destination = "#{uri}"
|
77
|
+
@last_To = "<#{uri}>"
|
78
|
+
end
|
79
|
+
|
80
|
+
def setdest source, options={}
|
81
|
+
@src = source
|
82
|
+
if options[:recv_from_this] and source.sock
|
83
|
+
@cxn.add_sock source.sock
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def recv_request(method)
|
88
|
+
begin
|
89
|
+
data = recv_something
|
90
|
+
rescue
|
91
|
+
raise "#{ @uri } timed out waiting for #{ method }"
|
92
|
+
end
|
93
|
+
unless data["message"].type == :request and Regexp.new(method) =~ data["message"].method
|
94
|
+
raise (data['message'].to_s || "Message is nil!")
|
95
|
+
end
|
96
|
+
data
|
97
|
+
end
|
98
|
+
|
99
|
+
def recv_response(code)
|
100
|
+
begin
|
101
|
+
data = recv_something
|
102
|
+
rescue
|
103
|
+
raise "#{ @uri } timed out waiting for #{ code }"
|
104
|
+
end
|
105
|
+
unless data["message"].type == :response and Regexp.new(code) =~ data["message"].status_code
|
106
|
+
raise "Expected #{ code}, got #{data["message"].status_code || data['message']}"
|
107
|
+
end
|
108
|
+
data
|
109
|
+
end
|
110
|
+
|
111
|
+
def send_response(code, retrans=nil, headers={})
|
112
|
+
msg = build_message headers, :response, code
|
113
|
+
send_something(msg, retrans)
|
114
|
+
end
|
115
|
+
|
116
|
+
def send_request(method, sdp=true, retrans=nil, headers={})
|
117
|
+
sdp="v=0
|
118
|
+
o=user1 53655765 2353687637 IN IP4 #{QuaffUtils.local_ip}
|
119
|
+
s=-
|
120
|
+
c=IN IP4 #{QuaffUtils.local_ip}
|
121
|
+
t=0 0
|
122
|
+
m=audio 7000 RTP/AVP 0
|
123
|
+
a=rtpmap:0 PCMU/8000"
|
124
|
+
|
125
|
+
msg = build_message headers, :request, method
|
126
|
+
send_something(msg, retrans)
|
127
|
+
end
|
128
|
+
|
129
|
+
def build_message headers, type, method_or_code
|
130
|
+
defaults = {
|
131
|
+
"From" => @last_From,
|
132
|
+
"To" => @last_To,
|
133
|
+
"Call-ID" => @cid,
|
134
|
+
"CSeq" => (/\d+/ =~ method_or_code) ? @last_CSeq.to_s : (method_or_code == "ACK") ? @last_CSeq.increment : "1 #{method_or_code}",
|
135
|
+
"Via" => @last_Via,
|
136
|
+
"Max-Forwards" => "70",
|
137
|
+
"Content-Length" => "0",
|
138
|
+
"User-Agent" => "Quaff SIP Scripting Engine",
|
139
|
+
"Contact" => "<sip:quaff@#{QuaffUtils.local_ip}:#{@cxn.local_port};transport=#{@cxn.transport};ob>",
|
140
|
+
}
|
141
|
+
|
142
|
+
if type == :request
|
143
|
+
defaults['Route'] = @routeset
|
144
|
+
else
|
145
|
+
defaults['Record-Route'] = @routeset
|
146
|
+
end
|
147
|
+
|
148
|
+
defaults.merge! headers
|
149
|
+
|
150
|
+
|
151
|
+
if type == :request
|
152
|
+
msg = "#{method_or_code} #{@sip_destination} SIP/2.0\r\n"
|
153
|
+
else
|
154
|
+
msg = "SIP/2.0 #{ method_or_code }\r\n"
|
155
|
+
end
|
156
|
+
|
157
|
+
defaults.each do |key, value|
|
158
|
+
if value.nil?
|
159
|
+
elsif not value.kind_of? Array
|
160
|
+
msg += "#{key}: #{value}\r\n"
|
161
|
+
else value.each do |subvalue|
|
162
|
+
msg += "#{key}: #{subvalue}\r\n"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
msg += "\r\n"
|
167
|
+
|
168
|
+
msg
|
169
|
+
end
|
170
|
+
|
171
|
+
def send_something(msg, retrans)
|
172
|
+
@cxn.send(msg, @src)
|
173
|
+
if retrans and (@transport == "UDP") then
|
174
|
+
@retrans = true
|
175
|
+
Thread.new do
|
176
|
+
timer = @t1
|
177
|
+
sleep timer
|
178
|
+
while @retrans do
|
179
|
+
#puts "Retransmitting on call #{ @cid }"
|
180
|
+
@cxn.send(msg, @src)
|
181
|
+
timer *=2
|
182
|
+
if timer < @t2 then
|
183
|
+
raise "Too many retransmits!"
|
184
|
+
end
|
185
|
+
sleep timer
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def end_call
|
192
|
+
@cxn.mark_call_dead @cid
|
193
|
+
end
|
194
|
+
|
195
|
+
def clear_tag str
|
196
|
+
str
|
197
|
+
end
|
198
|
+
|
199
|
+
def clone_details other_message
|
200
|
+
@headers['To'] = [clear_tag(other_message.header("To"))]
|
201
|
+
@headers['From'] = [clear_tag(other_message.header("From"))]
|
202
|
+
@headers['Route'] = [other_message.header("Route")]
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_next_hop header
|
206
|
+
/<sip:(.+@)?(.+):(\d+);(.*)>/ =~ header
|
207
|
+
sock = TCPSocket.new $2, $3
|
208
|
+
return TCPSource.new sock
|
209
|
+
end
|
210
|
+
|
211
|
+
def register username=@username, password=@password, expires="3600"
|
212
|
+
@username, @password = username, password
|
213
|
+
set_callee(@uri)
|
214
|
+
send_request("REGISTER", nil, nil, { "Expires" => expires.to_s })
|
215
|
+
response_data = recv_response("401|200")
|
216
|
+
if response_data['message'].status_code == "401"
|
217
|
+
send_request("ACK")
|
218
|
+
auth_hdr = gen_auth_header response_data['message'].header("WWW-Authenticate"), username, password, "REGISTER", @uri
|
219
|
+
update_branch
|
220
|
+
send_request("REGISTER", nil, nil, {"Authorization" => auth_hdr, "Expires" => expires.to_s})
|
221
|
+
recv_response("200")
|
222
|
+
end
|
223
|
+
return true
|
224
|
+
end
|
225
|
+
|
226
|
+
def unregister
|
227
|
+
register @username, @password, 0
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
data/lib/endpoint.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
# -*- coding: us-ascii -*-
|
2
|
+
require 'socket'
|
3
|
+
require 'thread'
|
4
|
+
require 'timeout'
|
5
|
+
require_relative './sip_parser.rb'
|
6
|
+
require_relative './sources.rb'
|
7
|
+
|
8
|
+
class BaseEndpoint
|
9
|
+
attr_accessor :msg_trace
|
10
|
+
|
11
|
+
def terminate
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_sock sock
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_call call_id=nil, *args
|
18
|
+
call_id ||= get_new_call_id
|
19
|
+
puts "Call-Id for endpoint on #{@lport} is #{call_id}" if @msg_trace
|
20
|
+
Call.new(self, call_id, *args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(lport)
|
24
|
+
@lport = lport
|
25
|
+
initialize_connection lport
|
26
|
+
initialize_queues
|
27
|
+
start
|
28
|
+
end
|
29
|
+
|
30
|
+
def local_port
|
31
|
+
@lport
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize_queues
|
35
|
+
@messages = {}
|
36
|
+
@call_ids = Queue.new
|
37
|
+
@dead_calls = {}
|
38
|
+
@sockets
|
39
|
+
end
|
40
|
+
|
41
|
+
def start
|
42
|
+
Thread.new do
|
43
|
+
loop do
|
44
|
+
recv_msg
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def queue_msg(msg, source)
|
50
|
+
puts "Endpoint on #{@lport} received #{msg} from #{source.inspect}" if @msg_trace
|
51
|
+
cid = @parser.message_identifier msg
|
52
|
+
if cid and not @dead_calls.has_key? cid then
|
53
|
+
unless @messages.has_key? cid then
|
54
|
+
|
55
|
+
add_call_id cid
|
56
|
+
@call_ids.enq cid
|
57
|
+
end
|
58
|
+
@messages[cid].enq({"message" => msg, "source" => source})
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_call_id cid
|
63
|
+
@messages[cid] ||= Queue.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_new_call_id time_limit=5
|
67
|
+
Timeout::timeout(time_limit) { @call_ids.deq }
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_new_message(cid, time_limit=5)
|
71
|
+
Timeout::timeout(time_limit) { @messages[cid].deq }
|
72
|
+
end
|
73
|
+
|
74
|
+
def mark_call_dead(cid)
|
75
|
+
@messages.delete cid
|
76
|
+
now = Time.now
|
77
|
+
@dead_calls[cid] = now + 30
|
78
|
+
@dead_calls = @dead_calls.keep_if {|k, v| v > now}
|
79
|
+
end
|
80
|
+
|
81
|
+
def send(data, source)
|
82
|
+
puts "Endpoint on #{@lport} sending #{data} to #{source.inspect}" if @msg_trace
|
83
|
+
source.send(@cxn, data)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
class TCPSIPEndpoint < BaseEndpoint
|
89
|
+
attr_accessor :sockets
|
90
|
+
|
91
|
+
def initialize_connection(lport)
|
92
|
+
@cxn = TCPServer.new(lport)
|
93
|
+
@parser = SipParser.new
|
94
|
+
@sockets = []
|
95
|
+
end
|
96
|
+
|
97
|
+
def transport
|
98
|
+
"TCP"
|
99
|
+
end
|
100
|
+
|
101
|
+
def new_source ip, port
|
102
|
+
return TCPSource.new ip, port
|
103
|
+
end
|
104
|
+
|
105
|
+
alias_method :new_connection, :new_source
|
106
|
+
|
107
|
+
def recv_msg
|
108
|
+
select_response = IO.select(@sockets, [], [], 0) || [[]]
|
109
|
+
readable = select_response[0]
|
110
|
+
for sock in readable do
|
111
|
+
recv_msg_from_sock sock
|
112
|
+
end
|
113
|
+
begin
|
114
|
+
if @cxn
|
115
|
+
sock = @cxn.accept_nonblock
|
116
|
+
@sockets.push sock if sock
|
117
|
+
end
|
118
|
+
rescue IO::WaitReadable, Errno::EINTR
|
119
|
+
sleep 0.3
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def recv_msg_from_sock(sock)
|
124
|
+
@parser.parse_start
|
125
|
+
msg = nil
|
126
|
+
while msg.nil? and not sock.closed? do
|
127
|
+
line = sock.gets
|
128
|
+
msg = @parser.parse_partial line
|
129
|
+
end
|
130
|
+
queue_msg msg, TCPSourceFromSocket.new(sock)
|
131
|
+
end
|
132
|
+
|
133
|
+
def add_sock sock
|
134
|
+
@sockets.push sock
|
135
|
+
end
|
136
|
+
|
137
|
+
def terminate
|
138
|
+
oldsockets = @sockets.dup
|
139
|
+
@sockets = []
|
140
|
+
oldsockets.each do |s| s.close unless s.closed? end
|
141
|
+
mycxn = @cxn
|
142
|
+
@cxn = nil
|
143
|
+
mycxn.close
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
class UDPSIPEndpoint < BaseEndpoint
|
149
|
+
|
150
|
+
def recv_msg
|
151
|
+
data, addrinfo = @cxn.recvfrom(65535)
|
152
|
+
@parser.parse_start
|
153
|
+
msg = @parser.parse_partial(data)
|
154
|
+
queue_msg msg, UDPSourceFromAddrinfo.new(addrinfo) unless msg.nil?
|
155
|
+
end
|
156
|
+
|
157
|
+
def transport
|
158
|
+
"UDP"
|
159
|
+
end
|
160
|
+
|
161
|
+
def new_source ip, port
|
162
|
+
return UDPSource.new ip, port
|
163
|
+
end
|
164
|
+
|
165
|
+
alias_method :new_connection, :new_source
|
166
|
+
|
167
|
+
def initialize_connection(lport)
|
168
|
+
@cxn = UDPSocket.new
|
169
|
+
@cxn.bind('0.0.0.0', lport)
|
170
|
+
@sockets = []
|
171
|
+
@parser = SipParser.new
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
data/lib/quaff.rb
ADDED
data/lib/sip_parser.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# -*- coding: us-ascii -*-
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
class SipMessage
|
5
|
+
attr_accessor :type, :method, :requri, :reason, :status_code, :headers, :body
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@headers = {}
|
9
|
+
@method, @status_code, @reason, @req_uri = nil
|
10
|
+
@body = ""
|
11
|
+
end
|
12
|
+
|
13
|
+
def all_headers hdr
|
14
|
+
return @headers[hdr]
|
15
|
+
end
|
16
|
+
|
17
|
+
def header hdr
|
18
|
+
return @headers[hdr][0] unless @headers[hdr].nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :first_header, :header
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"#{@method} #{@status_code} #{@headers}"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class SipParser
|
30
|
+
attr_reader :state
|
31
|
+
def parse_start
|
32
|
+
@buf = ""
|
33
|
+
@msg = SipMessage.new
|
34
|
+
@state = :blank
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_partial(data)
|
38
|
+
return nil if data.nil?
|
39
|
+
data.lines.each do |line|
|
40
|
+
if @state == :blank
|
41
|
+
parse_line_blank line
|
42
|
+
elsif @state == :parsing_body
|
43
|
+
parse_line_body line
|
44
|
+
else
|
45
|
+
parse_line_first_line_parsed line
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if @state == :done
|
49
|
+
return @msg
|
50
|
+
else
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def message_identifier(msg)
|
56
|
+
msg.header("Call-ID")
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_line_blank line
|
60
|
+
if line =~ %r!^([A-Z]+) (.+) SIP/2.0\r$!
|
61
|
+
@msg.type = :request
|
62
|
+
@msg.method = $1
|
63
|
+
@msg.requri = $2
|
64
|
+
@state = :first_line_parsed
|
65
|
+
elsif line =~ %r!^SIP/2.0 (\d+) (.+)\r$!
|
66
|
+
@msg.type = :response
|
67
|
+
@msg.status_code = $1
|
68
|
+
@msg.reason = $3 || ""
|
69
|
+
@state = :first_line_parsed
|
70
|
+
elsif line == "\r" or line == "\r\n"
|
71
|
+
# skip empty lines
|
72
|
+
else
|
73
|
+
raise line.inspect
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_line_first_line_parsed line
|
78
|
+
if line =~ /^\s+(.+)\r/
|
79
|
+
@msg.headers[@cur_hdr][-1] += " "
|
80
|
+
@msg.headers[@cur_hdr][-1] += $1
|
81
|
+
if $1 == "Content-Length"
|
82
|
+
@state = :got_content_length
|
83
|
+
else
|
84
|
+
@state = :middle_of_headers
|
85
|
+
end
|
86
|
+
elsif line =~ /^([-\w]+)\s*:\s*(.+)\r/
|
87
|
+
@msg.headers[$1] ||= []
|
88
|
+
@msg.headers[$1].push $2
|
89
|
+
@cur_hdr = $1
|
90
|
+
if $1 == "Content-Length"
|
91
|
+
@state = :got_content_length
|
92
|
+
else
|
93
|
+
@state = :middle_of_headers
|
94
|
+
end
|
95
|
+
elsif line == "\r" or line == "\r\n"
|
96
|
+
if @state == :got_content_length and @msg.header("Content-Length").to_i > 0
|
97
|
+
@state = :parsing_body
|
98
|
+
else
|
99
|
+
@state = :done
|
100
|
+
end
|
101
|
+
else raise line.inspect
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_line_body line
|
106
|
+
@msg.body << line
|
107
|
+
if line == "\r" or @msg.body.length >= @msg.header("Content-Length").to_i
|
108
|
+
@state = :done
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
def gen_nonce auth_pairs, username, passwd, method, sip_uri
|
115
|
+
a1 = username + ":" + auth_pairs["realm"] + ":" + passwd
|
116
|
+
a2 = method + ":" + sip_uri
|
117
|
+
ha1 = Digest::MD5::hexdigest(a1)
|
118
|
+
ha2 = Digest::MD5::hexdigest(a2)
|
119
|
+
digest = Digest::MD5.hexdigest(ha1 + ":" + auth_pairs["nonce"] + ":" + ha2)
|
120
|
+
return digest
|
121
|
+
end
|
122
|
+
|
123
|
+
def gen_auth_header auth_line, username, passwd, method, sip_uri
|
124
|
+
# Split auth line on commas
|
125
|
+
auth_pairs = {}
|
126
|
+
auth_line.sub("Digest ", "").split(",") .each do |pair|
|
127
|
+
key, value = pair.split "="
|
128
|
+
auth_pairs[key.gsub(" ", "")] = value.gsub("\"", "").gsub(" ", "")
|
129
|
+
end
|
130
|
+
digest = gen_nonce auth_pairs, username, passwd, method, sip_uri
|
131
|
+
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']}"!
|
132
|
+
# Return Authorization header with fields username, realm, nonce, uri, nc, cnonce, response, opaque
|
133
|
+
end
|
data/lib/sources.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
class Source
|
4
|
+
def remote_ip
|
5
|
+
@ip
|
6
|
+
end
|
7
|
+
|
8
|
+
def remote_port
|
9
|
+
@port
|
10
|
+
end
|
11
|
+
|
12
|
+
def close cxn
|
13
|
+
end
|
14
|
+
|
15
|
+
def sock
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class UDPSource < Source
|
21
|
+
def initialize ip, port
|
22
|
+
@ip, @port = ip, port
|
23
|
+
end
|
24
|
+
|
25
|
+
def send cxn, data
|
26
|
+
cxn.send(data, 0, @ip, @port)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class UDPSourceFromAddrinfo < UDPSource
|
31
|
+
def initialize addrinfo
|
32
|
+
@ip, @port = addrinfo[3], addrinfo[1]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class TCPSource < Source
|
38
|
+
attr_reader :sock
|
39
|
+
|
40
|
+
def initialize ip, port
|
41
|
+
@sock = TCPSocket.new ip, port
|
42
|
+
@port, @ip = port, ip
|
43
|
+
end
|
44
|
+
|
45
|
+
def send _, data
|
46
|
+
@sock.puts data
|
47
|
+
end
|
48
|
+
|
49
|
+
def close cxn
|
50
|
+
@sock.close
|
51
|
+
cxn.sockets.delete(@sock)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class TCPSourceFromSocket < TCPSource
|
56
|
+
def initialize sock
|
57
|
+
@sock = sock
|
58
|
+
@port, @ip = Socket.unpack_sockaddr_in(@sock.getpeername)
|
59
|
+
end
|
60
|
+
end
|
data/lib/utils.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module QuaffUtils
|
4
|
+
def QuaffUtils.local_ip
|
5
|
+
Socket.ip_address_list.select {|i| !(i.ipv6? || i.ipv4_loopback?)}[0].ip_address
|
6
|
+
end
|
7
|
+
|
8
|
+
def local_ipv6
|
9
|
+
Socket.ip_address_list.select {|i| !(i.ipv4? || i.ipv6_loopback?)}[0].ip_address
|
10
|
+
end
|
11
|
+
|
12
|
+
def pid
|
13
|
+
Process.pid
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_call_id
|
17
|
+
"#{pid}_#{Time.new.to_i}@#{local_ipv4}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def QuaffUtils.new_branch
|
21
|
+
"z9hG4bK#{Time.new.to_f}"
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: quaff
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rob Day
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-24 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: A Ruby library for writing SIP test scenarios
|
15
|
+
email: rkd@rkd.me.uk
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/quaff.rb
|
21
|
+
- lib/utils.rb
|
22
|
+
- lib/call.rb
|
23
|
+
- lib/sources.rb
|
24
|
+
- lib/sip_parser.rb
|
25
|
+
- lib/endpoint.rb
|
26
|
+
homepage: http://github.com/rkd91/quaff
|
27
|
+
licenses:
|
28
|
+
- GPL3
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 1.8.23
|
48
|
+
signing_key:
|
49
|
+
specification_version: 3
|
50
|
+
summary: Quaff
|
51
|
+
test_files: []
|