opensips-mi 0.0.5 → 0.0.10

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.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MWFhOGFlYTdhZTU4N2I1MDdjYzVkNDU4ZWE3MWM1MzgwOTJmNGYzMw==
5
- data.tar.gz: !binary |-
6
- ZjE5MWRkYmQ5MThiNGM4ZjdkY2QxYWMyYmMwMzc4ZjA4YjRjMDIyOA==
7
- !binary "U0hBNTEy":
8
- metadata.gz: !binary |-
9
- MDU0ODY2NGUwYTA4Y2RjM2M1NmJlMWRhNjJjZDcyMzgwNWM3OTdmMzZlOTcw
10
- ODU3M2JiY2IwYTNkYjk2ODlmNDYwOWVhYWMzYWI3NDdjZTVjNDFjMzM5MTFm
11
- YWM1NzMwYjkyZDUzMTY4NWQyYzM3Y2YzN2YzYzYwMGJjYWQzNjM=
12
- data.tar.gz: !binary |-
13
- Yzc5NWMzYjYwZjQ1NzJiMzA1NDYzMzU4OGU4ODQ5ODUyZGFkNmE0Y2Y0MGQ4
14
- Y2Q1MDRlZWMyZmQ4YmFiZTkyM2ZlZTc3NjdmOGU3NjZiOTE3MmMyZTczYTM4
15
- NDdhNWFjNmNlNmY4OWI3NmZjNjFjNWZlNTBlZWI3MWFmZWEzYzM=
2
+ SHA256:
3
+ metadata.gz: 135298241a7da04c8011bdc51fd903c62b04dbfb05de49cce7610544fee9f274
4
+ data.tar.gz: 25a7bede110b7b35b121baddb04c2da6588d6fb8de3d9914cc84f6fd79041ef2
5
+ SHA512:
6
+ metadata.gz: 273474a444d49d471180eb7051cd57dc34e1c8c21146429cb649fab1134c5b4c6f604c0762a3a97005c4f340c7c4d379e95a0e89935d7511a1f5c1654c7edbc6
7
+ data.tar.gz: 7aca0cdf36610f3f4db77864d215ec9b214acaebfcac25345e2f8042997614f62e44f47e8914f06717f0d1cb9186e62df391524a2ae5bcef7246f7c3adb278c4
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  Gemfile*.lock
8
8
  .bundle
9
9
  .config
10
+ .byebug_history
10
11
  coverage
11
12
  log
12
13
  InstalledFiles
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -1,3 +1,3 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
3
+ - 2.3.8
data/Gemfile CHANGED
@@ -1,9 +1,7 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
- group :test do
4
- gem 'coveralls', require: false
5
- gem 'simplecov' #, :require => false
6
- gem 'mocha'
7
- end
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gem 'simplecov', require: false, group: :test
8
6
 
9
7
  gemspec
data/README.md CHANGED
@@ -73,12 +73,14 @@ opensips = Opensips::MI.connect :fifo,
73
73
  require 'opensips/mi'
74
74
  opensips = Opensips::MI.connect :datagram,
75
75
  :host => "sipproxy.com",
76
- :port => 8809
76
+ :port => 8809,
77
+ :timeout => 5
77
78
  ```
78
79
  **Parameters hash:**
79
80
 
80
81
  * host: Hostname or IP address of OpenSIPs server
81
82
  * port: Datagram port. See mi_datagram module configuration parameter: `modparam("mi_datagram", "socket_name", "udp:192.168.2.133:8080")`
83
+ * timeout: Timeout in seconds to wait send/recv commands. Optional. Default 3 seconds.
82
84
 
83
85
  ### XMLRPC
84
86
  ```ruby
@@ -141,6 +143,7 @@ There are several helper methods which return conveniently formatted data:
141
143
  * cache_fetch
142
144
  * ul_show_contact
143
145
  * dlg_list
146
+ * ps
144
147
 
145
148
  See example files for details.
146
149
 
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/clean"
3
3
  require 'opensips/mi/version'
4
+ require 'rspec/core/rake_task'
4
5
 
5
6
  require "rdoc/task"
6
7
  Rake::RDocTask.new do |rd|
@@ -10,11 +11,6 @@ Rake::RDocTask.new do |rd|
10
11
  rd.title = "OpenSIPs management interface " << Opensips::MI::VERSION
11
12
  end
12
13
 
13
- require 'rake/testtask'
14
- Rake::TestTask.new(:test) do |test|
15
- test.libs << 'lib' << 'test'
16
- test.pattern = 'test/**/test_*.rb'
17
- #test.verbose = true
18
- end
14
+ RSpec::Core::RakeTask.new(:spec)
19
15
 
20
- task :default => :test
16
+ task :default => :spec
@@ -26,9 +26,6 @@ module Opensips
26
26
 
27
27
  # Interface to mi methods direct call
28
28
  def method_missing(md, *params, &block)
29
- response = command 'which'
30
- raise NoMethodError,
31
- "Method #{md} does not exists" unless response.rawdata.include?(md.to_s)
32
29
  response = command md.to_s, params
33
30
  # return special helper output if exists
34
31
  return response unless response.success
@@ -78,15 +75,15 @@ module Opensips
78
75
  #
79
76
  def uac_dlg method, ruri, hf, next_hop = ?., socket = ?., body = nil
80
77
  mandatory_hf = Array['To', 'From']
81
- mandatory_hf += ['Content-Type', 'Content-Length'] unless body.nil?
78
+ mandatory_hf += ['Content-Type'] unless body.nil?
82
79
  mandatory_hf.map{|h|h.downcase}.each do |n|
83
80
  raise ArgumentError,
84
81
  "Missing mandatory header #{n.capitalize}" unless hf.keys.map{|h| h.downcase}.include?(n)
85
82
  end
86
83
  # compile headers to string
87
84
  headers = hf.map{|name,val| name.eql?("nl") ? "" : "#{name}: #{val}"}.join "\r\n"
88
- headers << "\r\n"
89
-
85
+ headers << "\r\n\r\n"
86
+
90
87
  # set_header is a hack for xmlrpc which fails if headers are quoted
91
88
  params = [method, ruri, next_hop, socket, set_header(headers)]
92
89
  params << body unless body.nil?
@@ -122,7 +119,7 @@ module Opensips
122
119
  hf['To'] = "<#{uri}>" unless hf.keys.map{|k|k.downcase}.include?('to')
123
120
  hf['From'] = "<#{uri}>;tag=#{SecureRandom.hex}" unless hf.keys.map{|k|k.downcase}.include?('from')
124
121
  hf['Event'] = EVENTNOTIFY[event]
125
-
122
+
126
123
  uac_dlg "NOTIFY", uri, hf
127
124
  end
128
125
 
@@ -151,7 +148,6 @@ module Opensips
151
148
  'Event' => "message-summary",
152
149
  'Subscription-State'=> "active",
153
150
  'Content-Type' => "application/simple-message-summary",
154
- 'Content-Length' => mbody.map{|k,v| "#{k}: #{v}"}.join("\r\n").length,
155
151
  'nl' => "",
156
152
  ]
157
153
 
@@ -172,7 +168,6 @@ module Opensips
172
168
  "Invalid port #{params[:port]}" unless (1..(2**16-1)).include?(params[:port])
173
169
  true
174
170
  end
175
-
176
171
  end
177
172
  end
178
173
  end
@@ -10,7 +10,7 @@ module Opensips
10
10
  'Invalid parameter' unless data.is_a? Array
11
11
  raise EmptyResponseData,
12
12
  'Empty parameter array' if data.empty?
13
-
13
+
14
14
  if /^(?<code>\d+) (?<message>.+)$/ =~ data.shift.to_s
15
15
  @code = code.to_i
16
16
  @message = message
@@ -22,26 +22,34 @@ module Opensips
22
22
  @success = (200..299).include?(@code)
23
23
 
24
24
  # successfull responses have additional new line
25
- data.pop if @success
26
25
  @rawdata = data
27
26
  @result = nil
28
27
  end
29
-
28
+
30
29
  # Parse user locations records to Hash
31
30
  def ul_dump
32
- return nil unless /^Domain:: location table=\d+ records=(\d+)$/ =~ @rawdata.shift
33
- records = Hash.new
34
- aor = ''
31
+ res = {}
32
+ aor = nil
33
+ contact = nil
34
+
35
35
  @rawdata.each do |r|
36
- if /\tAOR:: (?<peer>.+)$/ =~ r
37
- aor = peer
38
- records[aor] = Hash.new
39
- end
40
- if /^\t{2,3}(?<key>[^:]+):: (?<val>.*)$/ =~ r
41
- records[aor][key] = val if aor
36
+ next if r.start_with?("Domain")
37
+ r = r.strip
38
+ key, val = r.split(":: ")
39
+
40
+ if key == "AOR"
41
+ aor = val
42
+ res[aor] = []
43
+ next
44
+ elsif key == "Contact"
45
+ contact = {}
46
+ res[aor] << contact
42
47
  end
48
+
49
+ contact[key.gsub(?-, ?_).downcase.to_sym] = val if key
43
50
  end
44
- @result = records
51
+
52
+ @result = res
45
53
  self
46
54
  end
47
55
 
@@ -73,24 +81,25 @@ module Opensips
73
81
  @result = OpenStruct.new res
74
82
  self
75
83
  end
76
-
84
+
77
85
  # returns Array of registered contacts
78
86
  def ul_show_contact
79
- res = Array.new
87
+ result = []
80
88
  @rawdata.each do |r|
81
- cont = Hash.new
82
- r.split(?;).each do |rec|
83
- if /^Contact:: (.*)$/ =~ rec
84
- cont[:contact] = $1
85
- else
86
- key,val = rec.split ?=
87
- cont[key.to_sym] = val
88
- end
89
+ _, contact = r.strip.split("Contact:: ")
90
+ next unless contact
91
+
92
+ params = contact.split(';')
93
+
94
+ res = {contact: params.shift}
95
+
96
+ params.each do |p|
97
+ key, val = p.split('=')
98
+ res[key.gsub(?-, ?_).downcase.to_sym] = val
89
99
  end
90
- res << cont
100
+ result << res
91
101
  end
92
- @result = res
93
- self
102
+ @result = result
94
103
  end
95
104
 
96
105
  # returns hash of dialogs
@@ -124,19 +133,39 @@ module Opensips
124
133
  self
125
134
  end
126
135
 
136
+ # returns array containing list of opensips processes
137
+ def ps
138
+ processes = []
139
+ @rawdata.each do |l|
140
+ l.slice! "Process:: "
141
+ h = {}
142
+
143
+ l.split(" ", 3).each do |x|
144
+ key, val = x.split("=", 2)
145
+ h[key.downcase.to_sym] = val
146
+ end
147
+
148
+ processes << OpenStruct.new(h)
149
+ end
150
+
151
+ @result = processes
152
+ self
153
+ end
154
+
127
155
  private
128
156
  def dr_gws_hash
129
- Hash[
130
- @rawdata.map do |gw|
131
- if /\AID::\s+(?<id>[^\s]+)\s+IP=(?<ip>[^:\s]+):?(?<port>\d+)?\s+Enabled=(?<status>yes|no)/ =~ gw
132
- [id, {
133
- enabled: status.eql?('yes'),
134
- ipaddr: ip,
135
- port: port
136
- }]
137
- end
157
+ return nil if @rawdata.empty?
158
+ res = {}
159
+ @rawdata.map do |gw|
160
+ if /\AID::\s+(?<id>[^\s]+)\s+IP=(?<ip>[^:\s]+):?(?<port>\d+)?\s+Enabled=(?<status>yes|no)/ =~ gw
161
+ res[id] = {
162
+ enabled: status.eql?('yes'),
163
+ ipaddr: ip,
164
+ port: port
165
+ }
138
166
  end
139
- ]
167
+ end
168
+ res.empty? ? nil : res
140
169
  end
141
170
 
142
171
  end # END class
@@ -1,8 +1,11 @@
1
+ require 'timeout'
2
+
1
3
  module Opensips
2
4
  module MI
3
5
  module Transport
4
6
  class Datagram < Opensips::MI::Command
5
7
  RECVMAXLEN = 2**16 - 1
8
+ TIMEOUT = 3
6
9
 
7
10
  class << self
8
11
  def init(params)
@@ -14,6 +17,7 @@ module Opensips
14
17
  host_valid? params
15
18
  @sock = UDPSocket.new
16
19
  @sock.connect params[:host], params[:port]
20
+ @timeout = params[:timeout].to_i
17
21
  end
18
22
 
19
23
  def command(cmd, params = [])
@@ -21,12 +25,20 @@ module Opensips
21
25
  params.each do |c|
22
26
  request << "#{c}\n"
23
27
  end
24
- @sock.send request, 0
28
+ Timeout::timeout(tout, nil, "Timeout send request to datagram MI") {
29
+ @sock.send request, 0
30
+ }
25
31
  # will raise Errno::ECONNREFUSED if failed to connect
26
- response, = @sock.recvfrom RECVMAXLEN
32
+ Timeout::timeout(tout,nil,"Timeout receive respond from datagram MI") {
33
+ response, = @sock.recvfrom RECVMAXLEN
34
+ }
27
35
  Opensips::MI::Response.new response.split(?\n)
28
36
  end
29
37
 
38
+ def tout
39
+ @timeout > 0 ? @timeout : TIMEOUT
40
+ end
41
+
30
42
  end
31
43
  end
32
44
  end
@@ -28,7 +28,7 @@ module Opensips
28
28
  params[:reply_dir]
29
29
  end
30
30
  raise ArgumentError,
31
- "Fifo reply directory does not exists #{@reply_dir}" unless Dir.exists? @reply_dir
31
+ "Fifo reply directory does not exists #{@reply_dir}" unless Dir.exist? @reply_dir
32
32
 
33
33
  # fifo_name is required parameter
34
34
  raise ArgumentError,
@@ -36,10 +36,10 @@ module Opensips
36
36
 
37
37
  @fifo_name = params[:fifo_name]
38
38
  raise ArgumentError,
39
- "OpenSIPs fifo_name file does not exist: #{@fifo_name}" unless File.exists? @fifo_name
39
+ "OpenSIPs fifo_name file does not exist: #{@fifo_name}" unless File.exist? @fifo_name
40
40
  raise ArgumentError,
41
41
  "File #{@fifo_name} is not pipe" unless File.pipe? @fifo_name
42
-
42
+
43
43
  # set finalizing method
44
44
  reply_file = File.expand_path(@reply_fifo, @reply_dir)
45
45
  ObjectSpace.define_finalizer(self, proc{self.class.finalize(reply_file)})
@@ -81,7 +81,7 @@ module Opensips
81
81
  end
82
82
 
83
83
  def self.finalize(reply_file)
84
- File.unlink(reply_file) if File.exists?(reply_file)
84
+ File.unlink(reply_file) if File.exist?(reply_file)
85
85
  end
86
86
 
87
87
  end
@@ -1,5 +1,5 @@
1
1
  module Opensips
2
2
  module MI
3
- VERSION = "0.0.5"
3
+ VERSION = "0.0.10"
4
4
  end
5
5
  end
@@ -3,21 +3,23 @@ lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'opensips/mi/version'
5
5
 
6
- Gem::Specification.new do |gem|
7
- gem.name = "opensips-mi"
8
- gem.version = Opensips::MI::VERSION
9
- gem.authors = ["Stas Kobzar"]
10
- gem.email = ["stas@modulis.ca"]
11
- gem.description = %q{Ruby module for interacting with OpenSIPs management interface}
12
- gem.summary = %q{OpenSIPs management interface}
13
- gem.homepage = "http://github.com/staskobzar/opensips-mi"
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "opensips-mi"
8
+ spec.version = Opensips::MI::VERSION
9
+ spec.licenses = ['MIT']
10
+ spec.authors = ["Stas Kobzar"]
11
+ spec.email = ["staskobzar@gmail.com"]
12
+ spec.description = %q{Ruby module for interacting with OpenSIPs management interface}
13
+ spec.summary = %q{OpenSIPs management interface}
14
+ spec.homepage = "http://github.com/staskobzar/opensips-mi"
14
15
 
15
- gem.files = `git ls-files`.split($/).reject{|f| %r|^examples/.*|.match f}
16
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
- gem.require_paths = ["lib"]
16
+ spec.files = `git ls-files`.split($/).reject{|f| %r|^examples/.*|.match f}
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
19
 
20
- gem.add_development_dependency('bundler', '~> 1.3')
21
- gem.add_development_dependency('rdoc')
22
- gem.add_development_dependency('rake')
20
+ spec.add_dependency "xmlrpc", "~> 0.3"
21
+
22
+ spec.add_development_dependency "bundler", "~>2.2.5"
23
+ spec.add_development_dependency "rake", "~>13.0.3"
24
+ spec.add_development_dependency "rspec", "~>3.10.0"
23
25
  end
@@ -0,0 +1,4 @@
1
+ include Opensips::MI
2
+
3
+ describe Command do
4
+ end
File without changes
File without changes
@@ -0,0 +1,117 @@
1
+ include Opensips::MI
2
+
3
+ describe Response do
4
+ it "must raise if parameter is not Array" do
5
+ expect {Response.new "foo"}.to raise_error InvalidResponseData
6
+ end
7
+
8
+ it "must raise if response data id empty array" do
9
+ expect {Response.new Array[]}.to raise_error EmptyResponseData
10
+ end
11
+
12
+ it "must raise if invalid response data" do
13
+ expect {Response.new(["invalid param","343",222])}.
14
+ to raise_error InvalidResponseData
15
+ end
16
+
17
+ it "must parse successfull response" do
18
+ resp = Response.new ["200 it is OK", "data", ""]
19
+ expect(resp.success).to be_truthy
20
+ expect(resp.code).to be(200)
21
+ expect(resp.message).to match("it is OK")
22
+ end
23
+
24
+ it "must parse unsuccessfull response" do
25
+ resp = Response.new ["500 command 'unknown' not available"]
26
+ expect(resp.success).to be_falsey
27
+ expect(resp.code).to be(500)
28
+ expect(resp.message).to match("command 'unknown' not available")
29
+ end
30
+
31
+ it "parse ul dump response" do
32
+ res = Response.new(fixture('ul_dump'))
33
+ ul = res.ul_dump
34
+ expect(ul.result["7962"]).not_to be_nil
35
+ expect(ul.result["7962"][0][:callid]).to match("5e7a1e47da91c41c")
36
+ end
37
+
38
+ it "process uptime response" do
39
+ res = Response.new [
40
+ "200 OK",
41
+ "Now:: Fri Apr 12 22:04:27 2013",
42
+ "Up since:: Thu Apr 11 21:43:01 2013",
43
+ "Up time:: 87686 [sec]",
44
+ ""
45
+ ]
46
+ resp = res.uptime
47
+ expect(resp.result.uptime).to be(87686)
48
+ expect(resp.result.since.thursday?).to be_truthy
49
+ expect(resp.result.since.hour).to be(21)
50
+ expect(resp.result.since.mon).to be(4)
51
+ end
52
+
53
+ it "must fetch cache value" do
54
+ res = Response.new [
55
+ "200 OK",
56
+ "userdid = [18005552211]",
57
+ ""
58
+ ]
59
+ resp= res.cache_fetch
60
+ expect(resp.result.userdid).to match("18005552211")
61
+ end
62
+
63
+ it "must return userloc contacts" do
64
+ contacts = ["200 OK",
65
+ "Contact:: <sip:7747@10.132.113.198>;q=;expires=100;flags=0x0;cflags=0x0;socket=<udp:10.130.8.21:5060>;methods=0x1F7F;user_agent=<PolycomSoundStationIP-SSIP_6000-UA/3.3.5.0247_0004f2f18103>",
66
+ "Contact:: <sip:7747@10.130.8.100;line=628f4ffdfa7316e>;q=;expires=3593;flags=0x0;cflags=0x0;socket=<udp:10.130.8.21:5060>;methods=0xFFFFFFFF;user_agent=<Linphone/3.5.2 (eXosip2/3.6.0)>",
67
+ ""]
68
+ response = Response.new contacts
69
+ res = response.ul_show_contact
70
+ expect(res.size).to be(2)
71
+ expect(res.first[:socket]).to match("<udp:10.130.8.21:5060>")
72
+ expect(res.last[:expires]).to match("3593")
73
+ end
74
+
75
+ it "must process dialogs list" do
76
+ response = Response.new fixture('dlg_list')
77
+ res = response.dlg_list.result
78
+ expect(res.size).to be(1)
79
+ expect(res["3212:2099935485"][:callid]).to match("1854719653")
80
+ end
81
+
82
+ it "must process dr_gw_status response in hash" do
83
+ gw_list = [
84
+ "200 OK",
85
+ "ID:: gw1 IP=212.182.133.202:5060 Enabled=no ",
86
+ "ID:: gw2 IP=213.15.222.97:5060 Enabled=yes",
87
+ "ID:: gw3 IP=200.182.132.201:5060 Enabled=yes",
88
+ "ID:: gw4 IP=200.182.135.204:5060 Enabled=yes",
89
+ "ID:: pstn1 IP=199.18.14.101:5060 Enabled=yes",
90
+ "ID:: pstn2 IP=199.18.14.102:5060 Enabled=no",
91
+ "ID:: pstn3 IP=199.18.12.103:5060 Enabled=yes",
92
+ "ID:: pstn4 IP=199.18.12.104:5060 Enabled=yes",
93
+ ""
94
+ ]
95
+ response = Response.new gw_list
96
+ drgws = response.dr_gw_status
97
+ expect(drgws.result.size).to be(8)
98
+ expect(drgws.result["pstn4"][:ipaddr]).to match("199.18.12.104")
99
+ expect(drgws.result["pstn3"][:port]).to match("5060")
100
+ expect(drgws.result["gw1"][:enabled]).to be_falsey
101
+ expect(drgws.result["gw4"][:enabled]).to be_truthy
102
+ end
103
+
104
+ it "must return raw data if dr_gw_status is run with arguments" do
105
+ gw = [ "200 OK", "Enabled:: yes", "" ]
106
+ response = Response.new gw
107
+ drgws = response.dr_gw_status
108
+ expect(drgws.enabled).to be_truthy
109
+ end
110
+
111
+ it "result must be empty if command send to dr_gw_status" do
112
+ response = Response.new [ "200 OK", "" ]
113
+ drgws = response.dr_gw_status
114
+ expect(drgws.result).to be_nil
115
+ expect(drgws.success).to be_truthy
116
+ end
117
+ end