opensips-mi 0.0.5 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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