opensips-mi 0.0.11 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +7 -0
- data/Gemfile +9 -4
- data/README.md +143 -114
- data/Rakefile +8 -12
- data/lib/opensips/mi/command.rb +85 -92
- data/lib/opensips/mi/transport/abstract.rb +64 -0
- data/lib/opensips/mi/transport/datagram.rb +40 -26
- data/lib/opensips/mi/transport/fifo.rb +51 -71
- data/lib/opensips/mi/transport/http.rb +40 -0
- data/lib/opensips/mi/transport/xmlrpc.rb +36 -22
- data/lib/opensips/mi/transport.rb +5 -1
- data/lib/opensips/mi/version.rb +3 -1
- data/lib/opensips/mi.rb +18 -12
- data/lib/opensips.rb +4 -0
- data/sig/opensips/mi.rbs +6 -0
- metadata +20 -82
- data/.gitignore +0 -30
- data/.rspec +0 -1
- data/.travis.yml +0 -3
- data/lib/opensips/mi/response.rb +0 -176
- data/opensips-mi.gemspec +0 -26
- data/spec/command_spec.rb +0 -4
- data/spec/fixtures/dlg_list +0 -20
- data/spec/fixtures/ul_dump +0 -168
- data/spec/response_spec.rb +0 -117
- data/spec/spec_helper.rb +0 -112
- data/spec/transport_spec.rb +0 -119
data/lib/opensips/mi/command.rb
CHANGED
@@ -1,45 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Opensips
|
2
4
|
module MI
|
5
|
+
# core class to send command to MI
|
6
|
+
# and return responses
|
3
7
|
class Command
|
8
|
+
attr_reader :transp
|
9
|
+
|
4
10
|
EVENTNOTIFY = {
|
5
11
|
# Aastra
|
6
|
-
aastra_check_cfg:
|
7
|
-
aastra_xml:
|
12
|
+
aastra_check_cfg: "check-sync",
|
13
|
+
aastra_xml: "aastra-xml",
|
8
14
|
# Digium
|
9
|
-
digium_check_cfg:
|
15
|
+
digium_check_cfg: "check-sync",
|
10
16
|
# Linksys
|
11
|
-
linksys_cold_restart:
|
12
|
-
linksys_warm_restart:
|
17
|
+
linksys_cold_restart: "reboot_now",
|
18
|
+
linksys_warm_restart: "restart_now",
|
13
19
|
# Polycom
|
14
|
-
polycom_check_cfg:
|
20
|
+
polycom_check_cfg: "check-sync",
|
15
21
|
# Sipura
|
16
|
-
sipura_check_cfg:
|
17
|
-
sipura_get_report:
|
22
|
+
sipura_check_cfg: "resync",
|
23
|
+
sipura_get_report: "report",
|
18
24
|
# Snom
|
19
|
-
snom_check_cfg:
|
20
|
-
snom_reboot:
|
25
|
+
snom_check_cfg: "check-sync;reboot=false",
|
26
|
+
snom_reboot: "check-sync;reboot=true",
|
21
27
|
# Cisco
|
22
|
-
cisco_check_cfg:
|
28
|
+
cisco_check_cfg: "check-sync",
|
23
29
|
# Avaya
|
24
|
-
avaya_check_cfg:
|
25
|
-
}
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
avaya_check_cfg: "check-sync"
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
def initialize(transp)
|
34
|
+
@transp = transp
|
35
|
+
end
|
36
|
+
|
37
|
+
# prepare args, pipe them to send to MI using transport
|
38
|
+
# and finally format response
|
39
|
+
def command(*params)
|
40
|
+
raise ErrorParams, "command missing method name" if params.empty?
|
41
|
+
|
42
|
+
transp.adapter_request(*params)
|
43
|
+
.then { |args| transp.send(*args) }
|
44
|
+
.then { |resp| transp.adapter_response(resp) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# meta methods call directly
|
48
|
+
def method_missing(cmd, *args)
|
49
|
+
command(cmd.to_s, *args)
|
37
50
|
end
|
38
51
|
|
52
|
+
def respond_to_missing?(_name, _include_private = false) = true
|
53
|
+
|
39
54
|
# = Interface to t_uac_dlg function of transaction (tm) module
|
40
55
|
# Very cool method from OpenSIPs. Can generate and send SIP request method to destination.
|
41
56
|
# Example of usage:
|
42
|
-
# - Send NOTIFY with special Event header to force restart SIP phone
|
57
|
+
# - Send NOTIFY with special Event header to force restart SIP phone
|
58
|
+
# (equivalent of ASterisk's "sip notify peer")
|
43
59
|
# - Send PUBLISH to trigger device state change notification
|
44
60
|
# - Send REFER to transfer call
|
45
61
|
# - etc., etc., etc.
|
@@ -50,10 +66,6 @@ module Opensips
|
|
50
66
|
# Example:
|
51
67
|
# hf["From"] => "Alice Liddell <sip:alice@wanderland.com>;tag=843887163"
|
52
68
|
#
|
53
|
-
# Special "nl" header with any value is used to input additional "\r\n". This is
|
54
|
-
# useful, for example, for message-summary event to separate application body. This is
|
55
|
-
# because t_uac_dlg expect body parameter as xml only.
|
56
|
-
#
|
57
69
|
# Thus, using multiple headers with same header-name is not possible with header hash.
|
58
70
|
# However, it is possible to use multiple header-values comma separated (rfc3261, section 7.3.1):
|
59
71
|
# hf["Route"] => "<sip:alice@atlanta.com>, <sip:bob@biloxi.com>"
|
@@ -67,60 +79,52 @@ module Opensips
|
|
67
79
|
# == Parameters
|
68
80
|
# method: SIP request method (NOTIFY, PUBLISH etc)
|
69
81
|
# ruri: Request URI, ex.: sip:555@10.0.0.55:5060
|
70
|
-
# hf: Headers array. Additional headers will be added to request.
|
82
|
+
# hf: Headers array. Additional headers will be added to request.
|
71
83
|
# At least "From" and "To" headers must be specify
|
72
84
|
# nhop: Next hop SIP URI (OBP); use "." if no value.
|
73
|
-
# socket: Local socket to be used for sending the request; use "." if no value.
|
74
|
-
#
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
"Missing mandatory header #{n.capitalize}" unless hf.keys.map{|h| h.downcase}.include?(n)
|
82
|
-
end
|
83
|
-
# compile headers to string
|
84
|
-
headers = hf.map{|name,val| name.eql?("nl") ? "" : "#{name}: #{val}"}.join "\r\n"
|
85
|
+
# socket: Local socket to be used for sending the request; use "." if no value.
|
86
|
+
# Ex.: udp:10.130.8.21:5060
|
87
|
+
# body: (optional, may not be present) request body (if present, requires the "Content-Type"
|
88
|
+
# and "Content-length" headers)
|
89
|
+
def uac_dlg(method, ruri, hdrs, next_hop = ".", socket = ".", body = nil)
|
90
|
+
validate_hf(hdrs)
|
91
|
+
|
92
|
+
headers = hdrs.map { |name, val| "#{name}: #{val}" }.join("\r\n")
|
85
93
|
headers << "\r\n\r\n"
|
86
94
|
|
87
|
-
|
88
|
-
params = [method, ruri, next_hop, socket, set_header(headers)]
|
95
|
+
params = [method, ruri, next_hop, socket, headers]
|
89
96
|
params << body unless body.nil?
|
90
|
-
|
91
|
-
command 't_uac_dlg', params
|
97
|
+
command "t_uac_dlg", params
|
92
98
|
end
|
93
99
|
|
94
100
|
# = NOTIFY check-sync like event
|
95
|
-
# NOTIFY Events to restart phone, force configuration reload or
|
96
|
-
# report for some SIP IP phone models.
|
101
|
+
# NOTIFY Events to restart phone, force configuration reload or
|
102
|
+
# report for some SIP IP phone models.
|
97
103
|
# The events list was taken from Asterisk configuration file (sip_notify.conf)
|
98
104
|
# Note that SIP IP phones usually should be configured to accept special notify
|
99
105
|
# event to reboot. For example, Polycom configuration option to enable special
|
100
106
|
# event would be:
|
101
107
|
# voIpProt.SIP.specialEvent.checkSync.alwaysReboot="1"
|
102
108
|
#
|
103
|
-
# This function will generate To/From/Event headers. Will use random tag for
|
104
|
-
# From header.
|
109
|
+
# This function will generate To/From/Event headers. Will use random tag for
|
110
|
+
# From header.
|
105
111
|
# *NOTE*: This function will not generate To header tag. This is not complying with
|
106
|
-
# SIP protocol specification (rfc3265). NOTIFY must be part of a subscription
|
112
|
+
# SIP protocol specification (rfc3265). NOTIFY must be part of a subscription
|
107
113
|
# dialog. However, it works for the most of the SIP IP phone models.
|
108
114
|
# == Parameters
|
109
|
-
# - uri: Valid client contact URI (sip:alice@10.0.0.100:5060).
|
115
|
+
# - uri: Valid client contact URI (sip:alice@10.0.0.100:5060).
|
110
116
|
# To get client URI use *ul_show_contact => contact* function
|
111
117
|
# - event: One of the events from EVENTNOTIFY constant hash
|
112
|
-
# - hf: Header fields. Add To/From header fields here if you do not want them
|
118
|
+
# - hf: Header fields. Add To/From header fields here if you do not want them
|
113
119
|
# to be auto-generated. Header field example:
|
114
120
|
# hf['To'] => '<sip:alice@wanderland.com>'
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
uac_dlg "NOTIFY", uri, hf
|
121
|
+
def event_notify(uri, event, hdrs = {})
|
122
|
+
hnames = hdrs.keys.map { |k| k.to_s.downcase } || []
|
123
|
+
hdrs["To"] = "<#{uri}>" unless hnames.include?("to")
|
124
|
+
hdrs["From"] = "<#{uri}>;tag=#{rand(1 << 32)}" unless hnames.include?("from")
|
125
|
+
hdrs["Event"] = EVENTNOTIFY[event] || event.to_s
|
126
|
+
|
127
|
+
uac_dlg "NOTIFY", uri, hdrs
|
124
128
|
end
|
125
129
|
|
126
130
|
# = Presence MWI
|
@@ -135,39 +139,28 @@ module Opensips
|
|
135
139
|
# - old: (optional) Old messages
|
136
140
|
# - urg_new: (optional) New urgent messages
|
137
141
|
# - urg_old: (optional) Old urgent messages
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
'Content-Type' => "application/simple-message-summary",
|
151
|
-
'nl' => "",
|
152
|
-
]
|
153
|
-
|
154
|
-
uac_dlg "NOTIFY", uri, hf.merge(mbody)
|
142
|
+
def mwi_update(uri, vmaccount, newmsgs, old = 0, urg_new = 0, urg_old = 0)
|
143
|
+
hbody = { "Messages-Waiting" => (newmsgs.positive? ? "yes" : "no"),
|
144
|
+
"Message-Account" => vmaccount,
|
145
|
+
"Voice-Message" => "#{newmsgs}/#{old} (#{urg_new}/#{urg_old})" }
|
146
|
+
hdrs = { "To" => "<#{uri}>",
|
147
|
+
"From" => "<#{uri}>;tag=#{rand(1 << 32)}",
|
148
|
+
"Event" => "message-summary",
|
149
|
+
"Subscription-State" => "active",
|
150
|
+
"Content-Type" => "application/simple-message-summary" }
|
151
|
+
|
152
|
+
body = hbody.map { |k, v| "#{k}: #{v}" }.join("\r\n") << "\r\n\r\n"
|
153
|
+
uac_dlg "NOTIFY", uri, hdrs, ".", ".", body
|
155
154
|
end
|
156
155
|
|
157
156
|
private
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
Socket.getaddrinfo(params[:host], nil) rescue
|
166
|
-
raise SocketError, "Invalid host #{params[:host]}"
|
167
|
-
raise SocketError,
|
168
|
-
"Invalid port #{params[:port]}" unless (1..(2**16-1)).include?(params[:port])
|
169
|
-
true
|
170
|
-
end
|
157
|
+
|
158
|
+
def validate_hf(headers)
|
159
|
+
names = headers&.keys&.map { |k| k.to_s.downcase } || []
|
160
|
+
return if names.include?("to") && names.include?("from")
|
161
|
+
|
162
|
+
raise ArgumentError, "invalid headers value. must be a hash and have 'To' and 'From' headers"
|
163
|
+
end
|
171
164
|
end
|
172
165
|
end
|
173
166
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Opensips
|
6
|
+
module MI
|
7
|
+
module Transport
|
8
|
+
# abstruct class for transport protocols
|
9
|
+
class Abstract
|
10
|
+
# send a command to connection and return response
|
11
|
+
def send(_command)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
# request adapter method
|
16
|
+
# by default if does jsonrpc v2 as string
|
17
|
+
# xmlrpc overload this message
|
18
|
+
def adapter_request(cmd, *args)
|
19
|
+
rpc = {
|
20
|
+
jsonrpc: "2.0",
|
21
|
+
id: rand(1 << 16),
|
22
|
+
method: cmd
|
23
|
+
}
|
24
|
+
|
25
|
+
unless args.empty?
|
26
|
+
params = args.flatten
|
27
|
+
rpc[:params] = params[0].is_a?(Hash) ? params[0] : params
|
28
|
+
end
|
29
|
+
|
30
|
+
JSON.generate(rpc)
|
31
|
+
end
|
32
|
+
|
33
|
+
# response adapter by default parses jsonrpc response
|
34
|
+
# to an object. xmlrp overloads this method
|
35
|
+
def adapter_response(body)
|
36
|
+
resp = JSON.parse(body)
|
37
|
+
if resp["result"]
|
38
|
+
{ result: resp["result"] }
|
39
|
+
elsif resp["error"]
|
40
|
+
{ error: resp["error"] }
|
41
|
+
else
|
42
|
+
{ error: { "message" => "invalid response: #{body}" } }
|
43
|
+
end
|
44
|
+
rescue JSON::ParserError => e
|
45
|
+
{ error: { "message" => %(JSON::ParserError: #{e}) } }
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def raise_invalid_params
|
51
|
+
raise Opensips::MI::ErrorParams,
|
52
|
+
"invalid params. Expecting a hash with :url and optional :timeout"
|
53
|
+
end
|
54
|
+
|
55
|
+
def seturi(url)
|
56
|
+
@uri = URI(url)
|
57
|
+
rescue URI::InvalidURIError
|
58
|
+
raise Opensips::MI::ErrorParams,
|
59
|
+
"invalid http host url: #{url}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,42 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "abstract"
|
4
|
+
require "socket"
|
5
|
+
require "timeout"
|
6
|
+
|
1
7
|
module Opensips
|
2
8
|
module MI
|
3
9
|
module Transport
|
4
|
-
|
5
|
-
|
6
|
-
|
10
|
+
# datagram UDP transport to communicate with MI
|
11
|
+
class Datagram < Abstract
|
12
|
+
def initialize(args)
|
13
|
+
super()
|
14
|
+
raise_invalid_params unless args.is_a?(Hash)
|
15
|
+
@host, @port, @timeout = args.values_at(:host, :port, :timeout)
|
16
|
+
raise_invalid_params if @host.nil? || @port.nil?
|
17
|
+
raise_invalid_port unless @port.to_i.between?(1, 1 << 16)
|
18
|
+
@timeout ||= 5
|
19
|
+
connect
|
20
|
+
end
|
7
21
|
|
8
|
-
|
9
|
-
|
10
|
-
|
22
|
+
def send(command)
|
23
|
+
Timeout.timeout(
|
24
|
+
@timeout,
|
25
|
+
Opensips::MI::ErrorSendTimeout,
|
26
|
+
"timeout send command to #{@host}:#{@port} within #{@timeout} sec"
|
27
|
+
) do
|
28
|
+
@sock.send command, 0
|
29
|
+
msg, = @sock.recvfrom(1500)
|
30
|
+
msg
|
11
31
|
end
|
12
32
|
end
|
13
33
|
|
14
|
-
|
15
|
-
host_valid? params
|
16
|
-
@sock = Socketry::UDP::Socket.connect(params[:host], params[:port])
|
17
|
-
@timeout = params[:timeout].to_i
|
18
|
-
end
|
34
|
+
protected
|
19
35
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
request << "#{c}\n"
|
24
|
-
end
|
25
|
-
response = send(request)
|
26
|
-
Opensips::MI::Response.new response.split(?\n)
|
36
|
+
def raise_invalid_params
|
37
|
+
raise Opensips::MI::ErrorParams,
|
38
|
+
"invalid params. Expecting a hash with :host, :port and optional :timeout"
|
27
39
|
end
|
28
40
|
|
29
|
-
def
|
30
|
-
|
41
|
+
def raise_invalid_port
|
42
|
+
raise Opensips::MI::ErrorParams, "invalid port '#{@port}'"
|
31
43
|
end
|
32
44
|
|
33
45
|
private
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
46
|
+
|
47
|
+
def connect
|
48
|
+
@sock = UDPSocket.new
|
49
|
+
Timeout.timeout(
|
50
|
+
@timeout,
|
51
|
+
Opensips::MI::ErrorResolveTimeout,
|
52
|
+
"failed to resolve address #{@host}:#{@port} within #{@timeout} sec"
|
53
|
+
) { @sock.connect(@host, @port) }
|
40
54
|
end
|
41
55
|
end
|
42
56
|
end
|
@@ -1,89 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "abstract"
|
4
|
+
require "fcntl"
|
5
|
+
require "pathname"
|
6
|
+
require "tempfile"
|
7
|
+
require "timeout"
|
8
|
+
|
1
9
|
module Opensips
|
2
10
|
module MI
|
3
11
|
module Transport
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
fifo.open
|
14
|
-
end
|
12
|
+
# FIFO transport to communicate with MI
|
13
|
+
class Fifo < Abstract
|
14
|
+
def initialize(args)
|
15
|
+
super()
|
16
|
+
raise_invalid_params unless args.is_a?(Hash)
|
17
|
+
@fifo_name, @reply_dir, @timeout = args.values_at(:fifo_name, :reply_dir, :timeout)
|
18
|
+
raise_invalid_params if @fifo_name.nil?
|
19
|
+
@reply_dir ||= "/tmp"
|
20
|
+
@timeout ||= 5
|
15
21
|
end
|
16
22
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
@reply_dir = if params[:reply_dir].nil?
|
26
|
-
'/tmp/'
|
27
|
-
else
|
28
|
-
params[:reply_dir]
|
29
|
-
end
|
30
|
-
raise ArgumentError,
|
31
|
-
"Fifo reply directory does not exists #{@reply_dir}" unless Dir.exist? @reply_dir
|
32
|
-
|
33
|
-
# fifo_name is required parameter
|
34
|
-
raise ArgumentError,
|
35
|
-
'Missing required parameter fifo_name' if params[:fifo_name].nil?
|
36
|
-
|
37
|
-
@fifo_name = params[:fifo_name]
|
38
|
-
raise ArgumentError,
|
39
|
-
"OpenSIPs fifo_name file does not exist: #{@fifo_name}" unless File.exist? @fifo_name
|
40
|
-
raise ArgumentError,
|
41
|
-
"File #{@fifo_name} is not pipe" unless File.pipe? @fifo_name
|
42
|
-
|
43
|
-
# set finalizing method
|
44
|
-
reply_file = File.expand_path(@reply_fifo, @reply_dir)
|
45
|
-
ObjectSpace.define_finalizer(self, proc{self.class.finalize(reply_file)})
|
23
|
+
def send(rpc)
|
24
|
+
reply_file = create_reply_file
|
25
|
+
write(reply_file, rpc)
|
26
|
+
read(reply_file)
|
27
|
+
ensure
|
28
|
+
reply_file.unlink if reply_file&.exist?
|
46
29
|
end
|
47
30
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
"Can not create reply pipe: #{fifo_file}" unless File.pipe?(fifo_file)
|
54
|
-
self
|
31
|
+
protected
|
32
|
+
|
33
|
+
def raise_invalid_params
|
34
|
+
raise Opensips::MI::ErrorParams,
|
35
|
+
"invalid params. Expecting a hash with :fifo_name and optional :reply_dir"
|
55
36
|
end
|
56
37
|
|
57
|
-
|
58
|
-
fd_w = IO::sysopen(@fifo_name, Fcntl::O_WRONLY)
|
59
|
-
fifo_w = IO.open(fd_w)
|
38
|
+
private
|
60
39
|
|
61
|
-
|
62
|
-
|
63
|
-
|
40
|
+
def write(reply_file, rpc)
|
41
|
+
Timeout.timeout(@timeout, Opensips::MI::ErrorSendTimeout) do
|
42
|
+
fifo_wr = IO.open(IO.sysopen(@fifo_name, Fcntl::O_WRONLY))
|
43
|
+
fifo_wr.syswrite(%(:#{reply_file.basename}:#{rpc}\n))
|
44
|
+
fifo_wr.flush
|
45
|
+
ensure
|
46
|
+
fifo_wr&.close
|
64
47
|
end
|
65
|
-
# additional new line to terminate command
|
66
|
-
request << ?\n
|
67
|
-
fifo_w.syswrite request
|
68
|
-
|
69
|
-
# read response
|
70
|
-
file = File.expand_path(File.expand_path(@reply_fifo,@reply_dir))
|
71
|
-
fd_r = IO::sysopen(file, Fcntl::O_RDONLY )
|
72
|
-
fifo_r = IO.open(fd_r)
|
73
|
-
|
74
|
-
response = Array[]
|
75
|
-
response << $_.chomp while fifo_r.gets
|
76
|
-
Opensips::MI::Response.new response
|
77
|
-
ensure
|
78
|
-
# make sure we always close files' descriptors
|
79
|
-
fifo_r.close if fifo_r
|
80
|
-
fifo_w.close if fifo_w
|
81
48
|
end
|
82
49
|
|
83
|
-
def
|
84
|
-
|
50
|
+
def read(reply_file)
|
51
|
+
Timeout.timeout(@timeout, Opensips::MI::ErrorSendTimeout) do
|
52
|
+
fifo_rd = IO.open(IO.sysopen(reply_file, Fcntl::O_RDONLY))
|
53
|
+
fifo_rd.read
|
54
|
+
ensure
|
55
|
+
fifo_rd&.close
|
56
|
+
end
|
85
57
|
end
|
86
58
|
|
59
|
+
def create_reply_file
|
60
|
+
tmpfile = Tempfile.create("opensips-mi-reply-", @reply_dir)
|
61
|
+
File.unlink(tmpfile)
|
62
|
+
|
63
|
+
File.mkfifo(tmpfile)
|
64
|
+
File.chmod(0o666, tmpfile)
|
65
|
+
Pathname.new(tmpfile)
|
66
|
+
end
|
87
67
|
end
|
88
68
|
end
|
89
69
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "net/http"
|
4
|
+
require_relative "abstract"
|
5
|
+
|
6
|
+
module Opensips
|
7
|
+
module MI
|
8
|
+
module Transport
|
9
|
+
# HTTP transport to communicate with MI
|
10
|
+
class HTTP < Abstract
|
11
|
+
def initialize(args)
|
12
|
+
super()
|
13
|
+
raise_invalid_params unless args.is_a?(Hash)
|
14
|
+
url, @timeout = args.values_at(:url, :timeout)
|
15
|
+
raise_invalid_params if url.nil?
|
16
|
+
seturi(url)
|
17
|
+
@timeout ||= 5
|
18
|
+
connect
|
19
|
+
end
|
20
|
+
|
21
|
+
def send(cmd)
|
22
|
+
resp = @client.post(@uri.path, cmd, { "Content-Type" => "application/json" })
|
23
|
+
unless resp.code.eql? "200"
|
24
|
+
raise Opensips::MI::ErrorHTTPReq,
|
25
|
+
"invalid MI HTTP response: #{resp.message}"
|
26
|
+
end
|
27
|
+
resp.body
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def connect
|
33
|
+
@client = Net::HTTP.new(@uri.host, @uri.port)
|
34
|
+
@client.read_timeout = @timeout
|
35
|
+
@client.write_timeout = @timeout
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,35 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "abstract"
|
4
|
+
require "xmlrpc/client"
|
5
|
+
|
1
6
|
module Opensips
|
2
7
|
module MI
|
3
8
|
module Transport
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
9
|
+
# XML-RPC protocol for MI
|
10
|
+
class Xmlrpc < Abstract
|
11
|
+
def initialize(args)
|
12
|
+
super()
|
13
|
+
raise_invalid_params unless args.is_a?(Hash)
|
14
|
+
url, @timeout = args.values_at(:url, :timeout)
|
15
|
+
raise_invalid_params if url.nil?
|
16
|
+
seturi(url)
|
17
|
+
@client = XMLRPC::Client.new2(@uri.to_s, nil, @timeout)
|
10
18
|
end
|
11
19
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
rescue => e
|
17
|
-
raise e
|
18
|
-
"Can not connect OpenSIPs server.\n#{e.message}"
|
20
|
+
def send(*cmd)
|
21
|
+
@client.call(*cmd)
|
22
|
+
rescue XMLRPC::FaultException => e
|
23
|
+
{ error: { "message" => e.message } }
|
24
|
+
rescue StandardError => e
|
25
|
+
raise Opensips::MI::ErrorHTTPReq, e
|
19
26
|
end
|
20
27
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
return Opensips::MI::Response.new response
|
28
|
+
# overload resonse adapter for xmlrpc
|
29
|
+
def adapter_response(resp)
|
30
|
+
if resp[:error]
|
31
|
+
resp
|
32
|
+
else
|
33
|
+
{ result: resp }
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
31
|
-
|
37
|
+
# overload request adapter for xmlrpc
|
38
|
+
def adapter_request(*args)
|
39
|
+
args.flatten => [cmd, *rest]
|
40
|
+
return [cmd] if rest.empty?
|
41
|
+
|
42
|
+
rest = rest.flatten
|
43
|
+
return [cmd, rest.first.map { |_, v| v }].flatten if rest.first.is_a?(Hash)
|
32
44
|
|
45
|
+
[cmd, rest].flatten
|
46
|
+
end
|
33
47
|
end
|
34
48
|
end
|
35
49
|
end
|
@@ -1,9 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative "transport/datagram"
|
4
|
+
require_relative "transport/fifo"
|
5
|
+
require_relative "transport/http"
|
3
6
|
require_relative "transport/xmlrpc"
|
4
7
|
|
5
8
|
module Opensips
|
6
9
|
module MI
|
10
|
+
# module transport protocols implementation
|
7
11
|
module Transport
|
8
12
|
end
|
9
13
|
end
|