ruby_ami 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +9 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +49 -0
- data/Rakefile +69 -0
- data/cucumber.yml +2 -0
- data/features/lexer.feature +260 -0
- data/features/step_definitions/lexer_steps.rb +207 -0
- data/features/support/ami_fixtures.yml +30 -0
- data/features/support/env.rb +16 -0
- data/features/support/introspective_lexer.rb +22 -0
- data/features/support/lexer_helper.rb +103 -0
- data/lib/ruby_ami.rb +29 -0
- data/lib/ruby_ami/Guardfile +6 -0
- data/lib/ruby_ami/action.rb +143 -0
- data/lib/ruby_ami/client.rb +187 -0
- data/lib/ruby_ami/error.rb +21 -0
- data/lib/ruby_ami/event.rb +10 -0
- data/lib/ruby_ami/lexer.rl.rb +302 -0
- data/lib/ruby_ami/lexer_machine.rl +87 -0
- data/lib/ruby_ami/metaprogramming.rb +17 -0
- data/lib/ruby_ami/response.rb +44 -0
- data/lib/ruby_ami/stream.rb +60 -0
- data/lib/ruby_ami/version.rb +3 -0
- data/ruby_ami.gemspec +40 -0
- data/spec/ruby_ami/action_spec.rb +163 -0
- data/spec/ruby_ami/client_spec.rb +324 -0
- data/spec/ruby_ami/error_spec.rb +7 -0
- data/spec/ruby_ami/event_spec.rb +7 -0
- data/spec/ruby_ami/response_spec.rb +7 -0
- data/spec/ruby_ami/stream_spec.rb +153 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/mock_server.rb +16 -0
- metadata +296 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Ben Langfeld, Jay Phillips
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# RubyAMI
|
2
|
+
RubyAMI is an AMI client library in Ruby and based on EventMachine with the sole purpose of providing an connection to the Asterisk Manager Interface. RubyAMI does not provide any features beyond connection management and protocol parsing. Actions are sent over the wire, and responses come back via callbacks. It's up to you to match these up into something useful. In this regard, RubyAMI is very similar to [Blather](https://github.com/sprsquish/blather) for XMPP or [Punchblock](https://github.com/adhearsion/punchblock), the Ruby 3PCC library. In fact, Punchblock uses RubyAMI under the covers for its Asterisk implementation, including an implementation of AsyncAGI.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
gem install ruby_ami
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
```ruby
|
9
|
+
require 'ruby_ami'
|
10
|
+
|
11
|
+
class MyAMIClient < RubyAMI::Client
|
12
|
+
def on_connect
|
13
|
+
puts "AMI Connected successfully"
|
14
|
+
write_action "Originate", ...some options...
|
15
|
+
end
|
16
|
+
|
17
|
+
def handle_event(event)
|
18
|
+
puts "Received an AMI event: #{event}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle_error(error)
|
22
|
+
...similar here...
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_response(response)
|
26
|
+
...similar here...
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
EM.run { MyAMIClient.run '127.0.0.1', 5038, 'admin', 'password' }
|
31
|
+
```
|
32
|
+
|
33
|
+
## Links:
|
34
|
+
* [Source](https://github.com/adhearsion/ruby_ami)
|
35
|
+
* [Documentation](http://rdoc.info/github/adhearsion/ruby_ami/master/frames)
|
36
|
+
* [Bug Tracker](https://github.com/adhearsion/ruby_ami/issues)
|
37
|
+
|
38
|
+
## Note on Patches/Pull Requests
|
39
|
+
|
40
|
+
* Fork the project.
|
41
|
+
* Make your feature addition or bug fix.
|
42
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
43
|
+
* Commit, do not mess with rakefile, version, or history.
|
44
|
+
* If you want to have your own version, that is fine but bump version in a commit by itself so I can ignore when I pull
|
45
|
+
* Send me a pull request. Bonus points for topic branches.
|
46
|
+
|
47
|
+
## Copyright
|
48
|
+
|
49
|
+
Copyright (c) 2011 Ben Langfeld, Jay Phillips. MIT licence (see LICENSE for details).
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'ci/reporter/rake/rspec'
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
7
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
8
|
+
spec.rspec_opts = '--color'
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
12
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
13
|
+
spec.rcov = true
|
14
|
+
spec.rspec_opts = '--color'
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'cucumber'
|
18
|
+
require 'cucumber/rake/task'
|
19
|
+
require 'ci/reporter/rake/cucumber'
|
20
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
21
|
+
t.cucumber_opts = %w{--tags ~@jruby} unless defined?(JRUBY_VERSION)
|
22
|
+
end
|
23
|
+
|
24
|
+
Cucumber::Rake::Task.new(:wip) do |t|
|
25
|
+
t.cucumber_opts = %w{-p wip}
|
26
|
+
end
|
27
|
+
|
28
|
+
task :default => [:ragel, :spec, :features]
|
29
|
+
task :ci => [:ragel, 'ci:setup:rspec', :spec, 'ci:setup:cucumber', :features]
|
30
|
+
|
31
|
+
require 'yard'
|
32
|
+
YARD::Rake::YardocTask.new
|
33
|
+
|
34
|
+
desc "Check Ragel version"
|
35
|
+
task :check_ragel_version do
|
36
|
+
ragel_version_match = `ragel --version`.match /(\d)\.(\d)+/
|
37
|
+
abort "Could not get Ragel version! Is it installed? You must have at least version 6.3" unless ragel_version_match
|
38
|
+
big, small = ragel_version_match.captures.map &:to_i
|
39
|
+
if big < 6 || (big == 6 && small < 3)
|
40
|
+
abort "Please upgrade Ragel! You're on version #{ragel_version_match[0]} and must be on 6.3 or later"
|
41
|
+
end
|
42
|
+
if (big == 6 && small < 7)
|
43
|
+
puts "WARNING: A change to Ruby since 1.9 affects the Ragel generated code."
|
44
|
+
puts "WARNING: You MUST be using Ragel version 6.7 or have patched it using"
|
45
|
+
puts "WARNING: the patch found at:"
|
46
|
+
puts "WARNING: http://www.mail-archive.com/ragel-users@complang.org/msg00440.html"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
RAGEL_FILES = %w[lib/ruby_ami/lexer.rl.rb]
|
51
|
+
|
52
|
+
desc "Used to regenerate the AMI source code files. Note: requires Ragel 6.3 or later be installed on your system"
|
53
|
+
task :ragel => :check_ragel_version do
|
54
|
+
RAGEL_FILES.each do |ragel_file|
|
55
|
+
ruby_file = ragel_file.sub ".rl.rb", ".rb"
|
56
|
+
puts `ragel -n -R #{ragel_file} -o #{ruby_file} 2>&1`
|
57
|
+
raise "Failed generating code from Ragel file #{ragel_file}" if $?.to_i.nonzero?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "Generates a GraphVis document showing the Ragel state machine"
|
62
|
+
task :visualize_ragel => :check_ragel_version do
|
63
|
+
RAGEL_FILES.each do |ragel_file|
|
64
|
+
base_name = File.basename ragel_file, ".rl.rb"
|
65
|
+
puts "ragel -V #{ragel_file} -o #{base_name}.dot 2>&1"
|
66
|
+
puts `ragel -V #{ragel_file} -o #{base_name}.dot 2>&1`
|
67
|
+
raise "Failed generating code from Ragel file #{ragel_file}" if $?.to_i.nonzero?
|
68
|
+
end
|
69
|
+
end
|
data/cucumber.yml
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
Feature: Lexing AMI
|
2
|
+
As a RubyAMI user
|
3
|
+
I want to lex the AMI protocol
|
4
|
+
So that I can control Asterisk asynchronously
|
5
|
+
|
6
|
+
Scenario: Lexing only the initial AMI version header
|
7
|
+
Given a new lexer
|
8
|
+
And a version header for AMI 1.0
|
9
|
+
|
10
|
+
When the buffer is lexed
|
11
|
+
|
12
|
+
Then the protocol should have lexed without syntax errors
|
13
|
+
And the version should be set to 1.0
|
14
|
+
|
15
|
+
Scenario: Lexing the initial AMI header and a login attempt
|
16
|
+
Given a new lexer
|
17
|
+
And a version header for AMI 1.0
|
18
|
+
And a normal login success with events
|
19
|
+
|
20
|
+
When the buffer is lexed
|
21
|
+
|
22
|
+
Then the protocol should have lexed without syntax errors
|
23
|
+
And 1 message should have been received
|
24
|
+
|
25
|
+
Scenario: Lexing the initial AMI header and then a Response:Follows section
|
26
|
+
Given a new lexer
|
27
|
+
And a version header for AMI 1.0
|
28
|
+
And a multi-line Response:Follows body of ragel_description
|
29
|
+
|
30
|
+
When the buffer is lexed
|
31
|
+
|
32
|
+
Then the protocol should have lexed without syntax errors
|
33
|
+
And the 'follows' body of 1 message received should equal ragel_description
|
34
|
+
|
35
|
+
Scenario: Lexing a Response:Follows section with no body
|
36
|
+
Given a new lexer
|
37
|
+
And a version header for AMI 1.0
|
38
|
+
And a multi-line Response:Follows body of empty_String
|
39
|
+
|
40
|
+
When the buffer is lexed
|
41
|
+
|
42
|
+
Then the protocol should have lexed without syntax errors
|
43
|
+
And the 'follows' body of 1 message received should equal empty_string
|
44
|
+
|
45
|
+
Scenario: Lexing a multi-line Response:Follows simulating the "core show channels" command
|
46
|
+
Given a new lexer
|
47
|
+
And a version header for AMI 1.0
|
48
|
+
Given a multi-line Response:Follows body of show_channels_from_wayne
|
49
|
+
|
50
|
+
When the buffer is lexed
|
51
|
+
|
52
|
+
Then the protocol should have lexed without syntax errors
|
53
|
+
And the 'follows' body of 1 message received should equal show_channels_from_wayne
|
54
|
+
|
55
|
+
Scenario: Lexing a multi-line Response:Follows simulating the "core show uptime" command
|
56
|
+
Given a new lexer
|
57
|
+
And a version header for AMI 1.0
|
58
|
+
Given a multi-line Response:Follows response simulating uptime
|
59
|
+
|
60
|
+
When the buffer is lexed
|
61
|
+
|
62
|
+
Then the protocol should have lexed without syntax errors
|
63
|
+
And the first message received should have a key "System uptime" with value "46 minutes, 30 seconds"
|
64
|
+
|
65
|
+
Scenario: Lexing a Response:Follows section which has a colon not on the first line
|
66
|
+
Given a new lexer
|
67
|
+
And a multi-line Response:Follows body of with_colon_after_first_line
|
68
|
+
|
69
|
+
When the buffer is lexed
|
70
|
+
|
71
|
+
Then the protocol should have lexed without syntax errors
|
72
|
+
And 1 message should have been received
|
73
|
+
And the 'follows' body of 1 message received should equal with_colon_after_first_line
|
74
|
+
|
75
|
+
@wip
|
76
|
+
Scenario: Lexing an immediate response with a colon in it.
|
77
|
+
Given a new lexer
|
78
|
+
And an immediate response with text "markq has 0 calls (max unlimited) in 'ringall' strategy (0s holdtime), W:0, C:0, A:0, SL:0.0% within 0s\r\n No Members\r\n No Callers\r\n\r\n\r\n\r\n"
|
79
|
+
|
80
|
+
When the buffer is lexed
|
81
|
+
|
82
|
+
Then the protocol should have lexed without syntax errors
|
83
|
+
And 1 message should have been received
|
84
|
+
And 1 message should be an immediate response with text "markq has 0 calls (max unlimited) in 'ringall' strategy (0s holdtime), W:0, C:0, A:0, SL:0.0% within 0s\r\n No Members\r\n No Callers"
|
85
|
+
|
86
|
+
Scenario: Lexing the initial AMI header and then an "Authentication Required" error.
|
87
|
+
Given a new lexer
|
88
|
+
And a version header for AMI 1.0
|
89
|
+
And an Authentication Required error
|
90
|
+
|
91
|
+
When the buffer is lexed
|
92
|
+
|
93
|
+
Then the protocol should have lexed without syntax errors
|
94
|
+
|
95
|
+
Scenario: Lexing the initial AMI header and then a Response:Follows section
|
96
|
+
Given a new lexer
|
97
|
+
And a version header for AMI 1.0
|
98
|
+
And a multi-line Response:Follows body of ragel_description
|
99
|
+
And a multi-line Response:Follows body of ragel_description
|
100
|
+
|
101
|
+
When the buffer is lexed
|
102
|
+
|
103
|
+
Then the protocol should have lexed without syntax errors
|
104
|
+
And the 'follows' body of 2 messages received should equal ragel_description
|
105
|
+
|
106
|
+
Scenario: Lexing a stanza without receiving an AMI header
|
107
|
+
Given a new lexer
|
108
|
+
And a normal login success with events
|
109
|
+
|
110
|
+
When the buffer is lexed
|
111
|
+
|
112
|
+
Then the protocol should have lexed without syntax errors
|
113
|
+
And 1 message should have been received
|
114
|
+
|
115
|
+
Scenario: Receiving an immediate response as soon as the socket is opened
|
116
|
+
Given a new lexer
|
117
|
+
And an immediate response with text "Immediate responses are so ridiculous"
|
118
|
+
|
119
|
+
When the buffer is lexed
|
120
|
+
|
121
|
+
Then the protocol should have lexed without syntax errors
|
122
|
+
And 1 message should have been received
|
123
|
+
And 1 message should be an immediate response with text "Immediate responses are so ridiculous"
|
124
|
+
|
125
|
+
Scenario: Receiving an immediate message surrounded by real messages
|
126
|
+
Given a new lexer
|
127
|
+
And a normal login success with events
|
128
|
+
And an immediate response with text "No queues have been created."
|
129
|
+
And a normal login success with events
|
130
|
+
|
131
|
+
When the buffer is lexed
|
132
|
+
|
133
|
+
Then the protocol should have lexed without syntax errors
|
134
|
+
And 3 messages should have been received
|
135
|
+
And 1 message should be an immediate response with text "No queues have been created."
|
136
|
+
|
137
|
+
Scenario: Receiving a Pong after a simulated login
|
138
|
+
Given a new lexer
|
139
|
+
And a version header for AMI 1.0
|
140
|
+
And a normal login success with events
|
141
|
+
And a Pong response with an ActionID of randomness
|
142
|
+
|
143
|
+
When the buffer is lexed
|
144
|
+
|
145
|
+
Then the protocol should have lexed without syntax errors
|
146
|
+
And 2 messages should have been received
|
147
|
+
|
148
|
+
Scenario: Ten Pong responses in a row
|
149
|
+
Given a new lexer
|
150
|
+
And 5 Pong responses without an ActionID
|
151
|
+
And 5 Pong responses with an ActionID of randomness
|
152
|
+
|
153
|
+
When the buffer is lexed
|
154
|
+
|
155
|
+
Then the protocol should have lexed without syntax errors
|
156
|
+
And 10 messages should have been received
|
157
|
+
|
158
|
+
Scenario: A Pong with an ActionID
|
159
|
+
Given a new lexer
|
160
|
+
And a Pong response with an ActionID of 1224469850.61673
|
161
|
+
|
162
|
+
When the buffer is lexed
|
163
|
+
|
164
|
+
Then the first message received should have a key "ActionID" with value "1224469850.61673"
|
165
|
+
|
166
|
+
Scenario: A response containing a floating point value
|
167
|
+
Given a new lexer
|
168
|
+
And a custom stanza named "call"
|
169
|
+
And the custom stanza named "call" has key "ActionID" with value "1224469850.61673"
|
170
|
+
And the custom stanza named "call" has key "Uniqueid" with value "1173223225.10309"
|
171
|
+
|
172
|
+
When the custom stanza named "call" is added to the buffer
|
173
|
+
And the buffer is lexed
|
174
|
+
|
175
|
+
Then the 1st message received should have a key "Uniqueid" with value "1173223225.10309"
|
176
|
+
|
177
|
+
Scenario: Receiving a message with custom key/value pairs
|
178
|
+
Given a new lexer
|
179
|
+
And a custom stanza named "person"
|
180
|
+
And the custom stanza named "person" has key "ActionID" with value "1224469850.61673"
|
181
|
+
And the custom stanza named "person" has key "Name" with value "Jay Phillips"
|
182
|
+
And the custom stanza named "person" has key "Age" with value "21"
|
183
|
+
And the custom stanza named "person" has key "Location" with value "San Francisco, CA"
|
184
|
+
And the custom stanza named "person" has key "x-header" with value "<FooBAR>"
|
185
|
+
And the custom stanza named "person" has key "Channel" with value "IAX2/127.0.0.1/4569-9904"
|
186
|
+
And the custom stanza named "person" has key "I have spaces" with value "i have trailing padding "
|
187
|
+
|
188
|
+
When the custom stanza named "person" is added to the buffer
|
189
|
+
And the buffer is lexed
|
190
|
+
|
191
|
+
Then the protocol should have lexed without syntax errors
|
192
|
+
And the first message received should have a key "Name" with value "Jay Phillips"
|
193
|
+
And the first message received should have a key "ActionID" with value "1224469850.61673"
|
194
|
+
And the first message received should have a key "Name" with value "Jay Phillips"
|
195
|
+
And the first message received should have a key "Age" with value "21"
|
196
|
+
And the first message received should have a key "Location" with value "San Francisco, CA"
|
197
|
+
And the first message received should have a key "x-header" with value "<FooBAR>"
|
198
|
+
And the first message received should have a key "Channel" with value "IAX2/127.0.0.1/4569-9904"
|
199
|
+
And the first message received should have a key "I have spaces" with value "i have trailing padding "
|
200
|
+
|
201
|
+
Scenario: Executing a stanza that was partially received
|
202
|
+
Given a new lexer
|
203
|
+
And a normal login success with events split into two pieces
|
204
|
+
|
205
|
+
When the buffer is lexed
|
206
|
+
|
207
|
+
Then the protocol should have lexed without syntax errors
|
208
|
+
And 1 message should have been received
|
209
|
+
|
210
|
+
Scenario: Receiving an AMI error followed by a normal event
|
211
|
+
Given a new lexer
|
212
|
+
And an AMI error whose message is "Missing action in request"
|
213
|
+
And a normal login success with events
|
214
|
+
|
215
|
+
When the buffer is lexed
|
216
|
+
|
217
|
+
Then the protocol should have lexed without syntax errors
|
218
|
+
And 1 AMI error should have been received
|
219
|
+
And the 1st AMI error should have the message "Missing action in request"
|
220
|
+
And 1 message should have been received
|
221
|
+
|
222
|
+
Scenario: Lexing an immediate response
|
223
|
+
Given a new lexer
|
224
|
+
And a normal login success with events
|
225
|
+
And an immediate response with text "Yes, plain English is sent sometimes over AMI."
|
226
|
+
And a normal login success with events
|
227
|
+
|
228
|
+
When the buffer is lexed
|
229
|
+
|
230
|
+
Then the protocol should have lexed without syntax errors
|
231
|
+
And 3 messages should have been received
|
232
|
+
And 1 message should be an immediate response with text "Yes, plain English is sent sometimes over AMI."
|
233
|
+
|
234
|
+
Scenario: Lexing an AMI event
|
235
|
+
Given a new lexer
|
236
|
+
And a custom event with name "NewChannelEvent" identified by "this_event"
|
237
|
+
And a custom header for event identified by "this_event" whose key is "Foo" and value is "Bar"
|
238
|
+
And a custom header for event identified by "this_event" whose key is "Channel" and value is "IAX2/127.0.0.1:4569-9904"
|
239
|
+
And a custom header for event identified by "this_event" whose key is "AppData" and value is "agi://localhost"
|
240
|
+
|
241
|
+
When the custom event identified by "this_event" is added to the buffer
|
242
|
+
And the buffer is lexed
|
243
|
+
|
244
|
+
Then the protocol should have lexed without syntax errors
|
245
|
+
And 1 event should have been received
|
246
|
+
And the 1st event should have the name "NewChannelEvent"
|
247
|
+
And the 1st event should have key "Foo" with value "Bar"
|
248
|
+
And the 1st event should have key "Channel" with value "IAX2/127.0.0.1:4569-9904"
|
249
|
+
And the 1st event should have key "AppData" with value "agi://localhost"
|
250
|
+
|
251
|
+
Scenario: Lexing an immediate packet with a colon in it (syntax error)
|
252
|
+
Given a new lexer
|
253
|
+
And syntactically invalid immediate_packet_with_colon
|
254
|
+
And a stanza break
|
255
|
+
|
256
|
+
When the buffer is lexed
|
257
|
+
|
258
|
+
Then 0 messages should have been received
|
259
|
+
And the protocol should have lexed with 1 syntax error
|
260
|
+
And the syntax error fixture named immediate_packet_with_colon should have been encountered
|