opensips-mi 0.0.11 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opensips-mi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stas Kobzar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-27 00:00:00.000000000 Z
11
+ date: 2023-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xmlrpc
@@ -24,72 +24,16 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.3'
27
- - !ruby/object:Gem::Dependency
28
- name: socketry
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.5'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '0.5'
41
- - !ruby/object:Gem::Dependency
42
- name: bundler
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rake
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: rspec
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- description: Ruby module for interacting with OpenSIPs management interface
27
+ description: Ruby module for interacting with OpenSIPs management interface. Supports
28
+ OpenSIPS v3+ MI with JSON-RPC protocol
84
29
  email:
85
30
  - staskobzar@gmail.com
86
31
  executables: []
87
32
  extensions: []
88
33
  extra_rdoc_files: []
89
34
  files:
90
- - ".gitignore"
91
- - ".rspec"
92
- - ".travis.yml"
35
+ - CODE_OF_CONDUCT.md
36
+ - CONTRIBUTING.md
93
37
  - Gemfile
94
38
  - LICENSE.txt
95
39
  - README.md
@@ -97,23 +41,22 @@ files:
97
41
  - lib/opensips.rb
98
42
  - lib/opensips/mi.rb
99
43
  - lib/opensips/mi/command.rb
100
- - lib/opensips/mi/response.rb
101
44
  - lib/opensips/mi/transport.rb
45
+ - lib/opensips/mi/transport/abstract.rb
102
46
  - lib/opensips/mi/transport/datagram.rb
103
- - lib/opensips/mi/transport/fifo.rb
47
+ - lib/opensips/mi/transport/http.rb
104
48
  - lib/opensips/mi/transport/xmlrpc.rb
105
49
  - lib/opensips/mi/version.rb
106
- - opensips-mi.gemspec
107
- - spec/command_spec.rb
108
- - spec/fixtures/dlg_list
109
- - spec/fixtures/ul_dump
110
- - spec/response_spec.rb
111
- - spec/spec_helper.rb
112
- - spec/transport_spec.rb
113
- homepage: http://github.com/staskobzar/opensips-mi
50
+ - sig/opensips/mi.rbs
51
+ homepage: https://github.com/staskobzar/opensips-mi
114
52
  licenses:
115
53
  - MIT
116
- metadata: {}
54
+ metadata:
55
+ allowed_push_host: https://rubygems.org
56
+ homepage_uri: https://github.com/staskobzar/opensips-mi
57
+ source_code_uri: https://github.com/staskobzar/opensips-mi
58
+ changelog_uri: https://github.com/staskobzar/opensips-mi/releases
59
+ github_repo: https://github.com/staskobzar/opensips-mi.git
117
60
  post_install_message:
118
61
  rdoc_options: []
119
62
  require_paths:
@@ -122,21 +65,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
65
  requirements:
123
66
  - - ">="
124
67
  - !ruby/object:Gem::Version
125
- version: '0'
68
+ version: 3.0.0
126
69
  required_rubygems_version: !ruby/object:Gem::Requirement
127
70
  requirements:
128
71
  - - ">="
129
72
  - !ruby/object:Gem::Version
130
73
  version: '0'
131
74
  requirements: []
132
- rubygems_version: 3.0.3
75
+ rubygems_version: 3.4.18
133
76
  signing_key:
134
77
  specification_version: 4
135
- summary: OpenSIPs management interface
136
- test_files:
137
- - spec/command_spec.rb
138
- - spec/fixtures/dlg_list
139
- - spec/fixtures/ul_dump
140
- - spec/response_spec.rb
141
- - spec/spec_helper.rb
142
- - spec/transport_spec.rb
78
+ summary: Ruby OpenSIPs management interface
79
+ test_files: []
data/.gitignore DELETED
@@ -1,30 +0,0 @@
1
- # Ignore temporary files
2
- .*swp
3
- *~
4
-
5
- *.gem
6
- *.rbc
7
- Gemfile*.lock
8
- .bundle
9
- .config
10
- .byebug_history
11
- coverage
12
- log
13
- InstalledFiles
14
- lib/bundler/man
15
- pkg
16
- rdoc
17
- spec/reports
18
- test/tmp
19
- test/version_tmp
20
- tmp
21
-
22
- # YARD artifacts
23
- .yardoc
24
- _yardoc
25
- doc/
26
-
27
- .DS_Store
28
- results.html
29
- html
30
-
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --require spec_helper
data/.travis.yml DELETED
@@ -1,3 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.6.3
@@ -1,176 +0,0 @@
1
- module Opensips
2
- module MI
3
- class Response
4
- attr_reader :code, :success, :message
5
- attr_reader :rawdata # raw data array
6
- attr_reader :result # formatted data
7
-
8
- def initialize(data)
9
- raise InvalidResponseData,
10
- 'Invalid parameter' unless data.is_a? Array
11
- raise EmptyResponseData,
12
- 'Empty parameter array' if data.empty?
13
-
14
- if /^(?<code>\d+) (?<message>.+)$/ =~ data.shift.to_s
15
- @code = code.to_i
16
- @message = message
17
- else
18
- raise InvalidResponseData,
19
- 'Invalid response parameter. Can not parse'
20
- end
21
-
22
- @success = (200..299).include?(@code)
23
-
24
- # successfull responses have additional new line
25
- @rawdata = data
26
- @result = nil
27
- end
28
-
29
- # Parse user locations records to Hash
30
- def ul_dump
31
- res = {}
32
- aor = nil
33
- contact = nil
34
-
35
- @rawdata.each do |r|
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
47
- end
48
-
49
- contact[key.gsub(?-, ?_).downcase.to_sym] = val if key
50
- end
51
-
52
- @result = res
53
- self
54
- end
55
-
56
- # returns struct
57
- def uptime
58
- res = Hash.new
59
- @rawdata.each do |r|
60
- next if /^Now::/ =~ r
61
- if /^Up since:: [^\s]+ (?'mon'[^\s]+)\s+(?'d'\d+) (?'h'\d+):(?'m'\d+):(?'s'\d+) (?'y'\d+)/ =~ r
62
- res[:since] = Time.local(y,mon,d,h,m,s)
63
- end
64
- if /^Up time:: (?'sec'\d+) / =~ r
65
- res[:uptime] = sec.to_i
66
- end
67
- end
68
- @result = OpenStruct.new res
69
- self
70
- end
71
-
72
- # returns struct
73
- def cache_fetch
74
- res = Hash.new
75
- @rawdata.each do |r|
76
- if /^(?'label'[^=]+)=\s+\[(?'value'[^\]]+)\]/ =~ r
77
- label.strip!
78
- res[label.to_sym] = value
79
- end
80
- end
81
- @result = OpenStruct.new res
82
- self
83
- end
84
-
85
- # returns Array of registered contacts
86
- def ul_show_contact
87
- result = []
88
- @rawdata.each do |r|
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
99
- end
100
- result << res
101
- end
102
- @result = result
103
- end
104
-
105
- # returns hash of dialogs
106
- def dlg_list
107
- # parse dialogs information into array
108
- # assuming new block always starts with "dialog:: hash=..."
109
- calls, key = Hash.new, nil
110
- @rawdata.each do |l|
111
- l.strip!
112
- if l.match(/^dialog::\s+hash=(.*)$/)
113
- key = $1
114
- calls[key] = Hash.new
115
- next
116
- end
117
- # building dialog array
118
- if l.match(/^([^:]+)::\s+(.*)$/)
119
- calls[key][$1.to_sym] = $2
120
- end
121
- end
122
- @result = calls
123
- self
124
- end
125
-
126
- def dr_gw_status
127
- return self if @rawdata.empty?
128
- if /\AEnabled::\s+(?<status>yes|no)/ =~ @rawdata[0]
129
- self.class.send(:define_method, :enabled, proc{status.eql?('yes')})
130
- return self
131
- end
132
- @result = dr_gws_hash
133
- self
134
- end
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
-
155
- private
156
- def dr_gws_hash
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
- }
166
- end
167
- end
168
- res.empty? ? nil : res
169
- end
170
-
171
- end # END class
172
-
173
- class InvalidResponseData < Exception;end
174
- class EmptyResponseData < Exception;end
175
- end
176
- end
@@ -1,90 +0,0 @@
1
- module Opensips
2
- module MI
3
- module Transport
4
- class Fifo < Opensips::MI::Command
5
- PERMISIONS = '0666'
6
- attr_accessor :reply_fifo # name of the reply fifo file
7
- attr_accessor :fifo_name # OpenSIPs fifo file. See mi_fifo module
8
- attr_accessor :reply_dir # path to directory with where the reply fif is located
9
-
10
- class << self
11
- def init(params)
12
- fifo = Fifo.new params
13
- fifo.open
14
- end
15
- end
16
-
17
- def initialize(params)
18
- # set default values
19
- @reply_fifo = if params[:reply_fifo].nil?
20
- "opensips_reply_" << SecureRandom.hex[0,8]
21
- else
22
- @reply_fifo = params[:reply_fifo]
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)})
46
- end
47
-
48
- def open
49
- # create fifo file
50
- fifo_file = File.expand_path(@reply_fifo, @reply_dir)
51
- Kernel.system "mkfifo -m #{PERMISIONS} #{fifo_file}"
52
- raise SystemCallError,
53
- "Can not create reply pipe: #{fifo_file}" unless File.pipe?(fifo_file)
54
- self
55
- end
56
-
57
- def command(cmd, params = [])
58
- fd_w = IO::sysopen(@fifo_name, Fcntl::O_WRONLY)
59
- fifo_w = IO.open(fd_w)
60
-
61
- request = ":#{cmd}:#{@reply_fifo}\n"
62
- params.each do |c|
63
- request << "#{c}\n"
64
- 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
- end
82
-
83
- def self.finalize(reply_file)
84
- File.unlink(reply_file) if File.exist?(reply_file)
85
- end
86
-
87
- end
88
- end
89
- end
90
- end
data/opensips-mi.gemspec DELETED
@@ -1,26 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'opensips/mi/version'
5
-
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"
15
-
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
-
20
- spec.add_dependency "xmlrpc", "~> 0.3"
21
- spec.add_dependency "socketry", "~> 0.5"
22
-
23
- spec.add_development_dependency "bundler"
24
- spec.add_development_dependency "rake"
25
- spec.add_development_dependency "rspec"
26
- end
data/spec/command_spec.rb DELETED
@@ -1,4 +0,0 @@
1
- include Opensips::MI
2
-
3
- describe Command do
4
- end
@@ -1,20 +0,0 @@
1
- 200 OK
2
- dialog:: hash=3212:2099935485
3
- state:: 4
4
- user_flags:: 0
5
- timestart:: 1365817158
6
- timeout:: 1365824358
7
- callid:: 1854719653
8
- from_uri:: sip:7747@voip.etsmtl.ca
9
- to_uri:: sip:95142842020@voip.etsmtl.ca
10
- caller_tag:: 1614235294
11
- caller_contact:: sip:7747@10.130.8.100
12
- callee_cseq:: 0
13
- caller_route_set::
14
- caller_bind_addr:: udp:10.130.8.21:5060
15
- callee_tag:: as2e60b080
16
- callee_contact:: sip:95142842020@10.130.8.240:5060
17
- caller_cseq:: 20
18
- callee_route_set::
19
- callee_bind_addr:: udp:10.130.8.21:5060
20
-
@@ -1,168 +0,0 @@
1
- 200 OK
2
- Domain:: location table=512 records=15
3
- AOR:: 7962
4
- Contact:: sip:7962@10.164.112.13 Q=
5
- Expires:: 143
6
- Callid:: 5e7a1e47da91c41c
7
- Cseq:: 31401
8
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
9
- State:: CS_SYNC
10
- Flags:: 0
11
- Cflag:: 0
12
- Socket:: udp:10.130.8.21:5060
13
- Methods:: 7551
14
- AOR:: 7329
15
- Contact:: sip:7329@10.132.113.171 Q=
16
- Expires:: 95
17
- Callid:: 504e5ee5baf3e7d8
18
- Cseq:: 10412
19
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
20
- State:: CS_SYNC
21
- Flags:: 0
22
- Cflag:: 0
23
- Socket:: udp:10.130.8.21:5060
24
- Methods:: 7551
25
- AOR:: 7863
26
- Contact:: sip:7863@10.132.113.172 Q=
27
- Expires:: 142
28
- Callid:: d808729f9d47ec2d
29
- Cseq:: 28091
30
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
31
- State:: CS_SYNC
32
- Flags:: 0
33
- Cflag:: 0
34
- Socket:: udp:10.130.8.21:5060
35
- Methods:: 7551
36
- AOR:: 3812
37
- Contact:: sip:3812@10.132.113.163 Q=
38
- Expires:: 58
39
- Callid:: a59c4a46f79d7317
40
- Cseq:: 19050
41
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
42
- State:: CS_SYNC
43
- Flags:: 0
44
- Cflag:: 0
45
- Socket:: udp:10.130.8.21:5060
46
- Methods:: 7551
47
- AOR:: 3810
48
- Contact:: sip:3810@10.132.113.166 Q=
49
- Expires:: 87
50
- Callid:: dd38e788e386a79d
51
- Cseq:: 18259
52
- User-agent:: Avaya IP Phone 1140E (SIP1140e.04.03.12.00)
53
- State:: CS_SYNC
54
- Flags:: 0
55
- Cflag:: 0
56
- Socket:: udp:10.130.8.21:5060
57
- Methods:: 7551
58
- AOR:: 7519
59
- Contact:: sip:7519@10.132.113.174 Q=
60
- Expires:: 63
61
- Callid:: f0a2566a33a42295
62
- Cseq:: 28149
63
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
64
- State:: CS_SYNC
65
- Flags:: 0
66
- Cflag:: 0
67
- Socket:: udp:10.130.8.21:5060
68
- Methods:: 7551
69
- AOR:: 7259
70
- Contact:: sip:7259@10.164.112.18 Q=
71
- Expires:: 32
72
- Callid:: 7f0d07e8c1415bbc
73
- Cseq:: 32493
74
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
75
- State:: CS_SYNC
76
- Flags:: 0
77
- Cflag:: 0
78
- Socket:: udp:10.130.8.21:5060
79
- Methods:: 7551
80
- AOR:: 7577
81
- Contact:: sip:7577@10.132.113.178 Q=
82
- Expires:: 159
83
- Callid:: 5eca2b3d1062416b
84
- Cseq:: 12767
85
- User-agent:: Avaya IP Phone 1140E (SIP1140e.04.03.12.00)
86
- State:: CS_SYNC
87
- Flags:: 0
88
- Cflag:: 0
89
- Socket:: udp:10.130.8.21:5060
90
- Methods:: 7551
91
- AOR:: 3901
92
- Contact:: sip:3901@10.132.113.160 Q=
93
- Expires:: 132
94
- Callid:: a57d709ba8c7d6d7
95
- Cseq:: 27476
96
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
97
- State:: CS_SYNC
98
- Flags:: 0
99
- Cflag:: 0
100
- Socket:: udp:10.130.8.21:5060
101
- Methods:: 7551
102
- AOR:: 7806
103
- Contact:: sip:7806@10.132.113.175 Q=
104
- Expires:: 132
105
- Callid:: 0fb4baf2782a89b3
106
- Cseq:: 23944
107
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
108
- State:: CS_SYNC
109
- Flags:: 0
110
- Cflag:: 0
111
- Socket:: udp:10.130.8.21:5060
112
- Methods:: 7551
113
- AOR:: 7402
114
- Contact:: sip:7402@10.132.113.168 Q=
115
- Expires:: 47981
116
- Callid:: ef8fa87d63302751
117
- Cseq:: 13537
118
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
119
- State:: CS_SYNC
120
- Flags:: 0
121
- Cflag:: 0
122
- Socket:: udp:10.130.8.21:5060
123
- Methods:: 7551
124
- AOR:: 3822
125
- Contact:: sip:3822@10.132.113.162 Q=
126
- Expires:: 97
127
- Callid:: f534f0b8d8a070e7
128
- Cseq:: 50129
129
- User-agent:: Avaya IP Phone 1220 (SIP12x0.04.03.12.00)
130
- State:: CS_SYNC
131
- Flags:: 0
132
- Cflag:: 0
133
- Socket:: udp:10.130.8.21:5060
134
- Methods:: 7551
135
- AOR:: 7641
136
- Contact:: sip:7641@10.164.112.28 Q=
137
- Expires:: 98
138
- Callid:: df0f22a80302737a
139
- Cseq:: 33571
140
- User-agent:: Avaya IP Phone 1140E (SIP1140e.04.03.12.00)
141
- State:: CS_SYNC
142
- Flags:: 0
143
- Cflag:: 0
144
- Socket:: udp:10.130.8.21:5060
145
- Methods:: 7551
146
- AOR:: 7423
147
- Contact:: sip:7423@10.164.113.25 Q=
148
- Expires:: 109
149
- Callid:: ab10dabda2f0f257
150
- Cseq:: 39074
151
- User-agent:: Avaya IP Phone 1120E (SIP1120e.04.03.12.00)
152
- State:: CS_SYNC
153
- Flags:: 0
154
- Cflag:: 0
155
- Socket:: udp:10.130.8.21:5060
156
- Methods:: 7551
157
- AOR:: 7747
158
- Contact:: sip:7747@10.132.113.198 Q=
159
- Expires:: 69
160
- Callid:: 8c026d6f-9afd173e-c4d03305@10.132.113.198
161
- Cseq:: 41286
162
- User-agent:: PolycomSoundStationIP-SSIP_6000-UA/3.3.5.0247_0004f2f18103
163
- State:: CS_SYNC
164
- Flags:: 0
165
- Cflag:: 0
166
- Socket:: udp:10.130.8.21:5060
167
- Methods:: 8063
168
-