ruby_ami 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +4 -1
- data/CHANGELOG.md +7 -0
- data/Guardfile +13 -5
- data/README.md +0 -16
- data/Rakefile +16 -31
- data/benchmarks/lexer.rb +47 -0
- data/features/lexer.feature +5 -48
- data/features/step_definitions/lexer_steps.rb +1 -5
- data/features/support/lexer_helper.rb +0 -5
- data/lib/ruby_ami/agi_result_parser.rb +2 -2
- data/lib/ruby_ami/error.rb +1 -0
- data/lib/ruby_ami/lexer.rb +148 -0
- data/lib/ruby_ami/version.rb +1 -1
- data/ruby_ami.gemspec +6 -4
- data/spec/ruby_ami/agi_result_parser_spec.rb +9 -0
- data/spec/ruby_ami/stream_spec.rb +26 -0
- metadata +38 -9
- data/lib/ruby_ami/lexer.rl.rb +0 -304
- data/lib/ruby_ami/lexer_machine.rl +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5e6e035e1967a44a905b10ad704dbac564d4c90
|
4
|
+
data.tar.gz: 20f7fd7d4a8061c6e15dffa0b2f178645ef31b32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5fafdd9ed56328ed906339a18d1ad8e3671985b6fd5a834ce9c6fe3d6e7ffccd7c44c2f0960d7f6f77358b3739ea6bb3e522f92191209e30b2ad12648cf0e7d
|
7
|
+
data.tar.gz: 0dc58ad3d2f2e3bf7e88fb66c43230d8c513bd61f7bdf7a196bd816e94fc78d7542b0eb55b26c154a7de6c26a56e4c881cd65ea52988192d5dd0d9e6d24be0f2
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,18 @@
|
|
1
1
|
# [develop](https://github.com/adhearsion/ruby_ami)
|
2
2
|
|
3
|
+
# [2.1.0](https://github.com/adhearsion/ruby_ami/compare/v2.0.0...v2.1.0) - [2013-05-29](https://rubygems.org/gems/ruby_ami/versions/2.1.0)
|
4
|
+
* Enhancement: Replace Ragel parser with pure Ruby version, which is much more performant and simpler
|
5
|
+
* Bugfix: Handle AGI 5xx responses
|
6
|
+
|
3
7
|
# [2.0.0](https://github.com/adhearsion/ruby_ami/compare/v1.3.3...v2.0.0) - [2013-04-15](https://rubygems.org/gems/ruby_ami/versions/2.0.0)
|
4
8
|
* Major refactoring for simplification and performance
|
5
9
|
* Actions are no longer synchronised on the wire since ActionID is now a reliable method of response/event association
|
6
10
|
* Callbacks are no longer required. #send_action now simply blocks waiting for a response
|
7
11
|
* Client still starts up two Streams, one for actions and one for events, but only for possible performance gains. It is possible to use Stream directly since it now does its own login and response association. Client is a very thin routing layer. It's encouraged that if you expect low traffic, you should use Stream directly. Client may be removed in v3.0.
|
8
12
|
|
13
|
+
# [1.3.4](https://github.com/adhearsion/ruby_ami/compare/v1.3.3...v1.3.4) - [2013-04-25](https://rubygems.org/gems/ruby_ami/versions/1.3.4)
|
14
|
+
* Bugfix: Handle AGI 5xx responses
|
15
|
+
|
9
16
|
# [1.3.3](https://github.com/adhearsion/ruby_ami/compare/v1.3.2...v1.3.3) - [2013-04-09](https://rubygems.org/gems/ruby_ami/versions/1.3.3)
|
10
17
|
* Bugfix: DBGet actions are now not terminated specially
|
11
18
|
|
data/Guardfile
CHANGED
@@ -1,9 +1,17 @@
|
|
1
|
-
guard '
|
2
|
-
watch("lib/ruby_ami/lexer_machine.rl") { `rake ragel` }
|
3
|
-
end
|
4
|
-
|
5
|
-
guard 'rspec', :version => 2, :cli => '--format documentation' do
|
1
|
+
guard 'rspec', :cli => '--format documentation' do
|
6
2
|
watch(%r{^spec/.+_spec\.rb$})
|
7
3
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
8
4
|
watch('spec/spec_helper.rb') { "spec/" }
|
9
5
|
end
|
6
|
+
|
7
|
+
guard 'cucumber', cli: '--profile default --color --format progress' do
|
8
|
+
watch("lib/ruby_ami/lexer.rb") { 'features' }
|
9
|
+
watch(%r{^features/.+\.feature$})
|
10
|
+
watch(%r{^features/support/.+$}) { 'features' }
|
11
|
+
watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
|
12
|
+
end
|
13
|
+
|
14
|
+
guard 'rake', task: 'benchmark' do
|
15
|
+
watch("lib/ruby_ami/lexer.rb")
|
16
|
+
watch(/benchmarks\/*/)
|
17
|
+
end
|
data/README.md
CHANGED
@@ -31,22 +31,6 @@ client.start
|
|
31
31
|
Celluloid::Actor.join client
|
32
32
|
```
|
33
33
|
|
34
|
-
## Development Requirements
|
35
|
-
|
36
|
-
ruby_ami uses [ragel](http://www.complang.org/ragel/) to generate some of it's files.
|
37
|
-
|
38
|
-
On OS X (if you use homebrew):
|
39
|
-
|
40
|
-
brew install ragel
|
41
|
-
|
42
|
-
On Linux:
|
43
|
-
|
44
|
-
apt-get install ragel OR yum install ragel
|
45
|
-
|
46
|
-
Once you are inside the repository, before anything else, you will want to run:
|
47
|
-
|
48
|
-
rake ragel
|
49
|
-
|
50
34
|
## Links:
|
51
35
|
* [Source](https://github.com/adhearsion/ruby_ami)
|
52
36
|
* [Documentation](http://rdoc.info/github/adhearsion/ruby_ami/master/frames)
|
data/Rakefile
CHANGED
@@ -17,37 +17,22 @@ Cucumber::Rake::Task.new(:wip) do |t|
|
|
17
17
|
t.cucumber_opts = %w{-p wip}
|
18
18
|
end
|
19
19
|
|
20
|
-
task :default => [:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
20
|
+
task :default => [:spec, :features]
|
21
|
+
require 'timeout'
|
22
|
+
desc "Run benchmarks"
|
23
|
+
task :benchmark do
|
24
|
+
begin
|
25
|
+
Timeout.timeout(120) do
|
26
|
+
glob = File.expand_path("../benchmarks/*.rb", __FILE__)
|
27
|
+
Dir[glob].each { |benchmark| load benchmark }
|
28
|
+
end
|
29
|
+
rescue Exception, Timeout::Error => ex
|
30
|
+
puts "ERROR: Couldn't complete benchmark: #{ex.class}: #{ex}"
|
31
|
+
puts " #{ex.backtrace.join("\n ")}"
|
32
|
+
|
33
|
+
exit 1 unless ENV['CI'] # Hax for running benchmarks on Travis
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
run_ragel '-n -R'
|
39
|
-
end
|
40
|
-
|
41
|
-
desc "Generates a GraphVis document showing the Ragel state machine"
|
42
|
-
task :visualize_ragel => :check_ragel_version do
|
43
|
-
run_ragel '-V', 'dot'
|
44
|
-
end
|
45
|
-
|
46
|
-
def run_ragel(options = nil, extension = 'rb')
|
47
|
-
ragel_file = 'lib/ruby_ami/lexer.rl.rb'
|
48
|
-
base_file = ragel_file.sub ".rl.rb", ""
|
49
|
-
command = ["ragel", options, "#{ragel_file} -o #{base_file}.#{extension} 2>&1"].compact.join ' '
|
50
|
-
puts "Running command '#{command}'"
|
51
|
-
puts `#{command}`
|
52
|
-
raise "Failed generating code from Ragel file #{ragel_file}" if $?.to_i.nonzero?
|
53
|
-
end
|
37
|
+
require 'yard'
|
38
|
+
YARD::Rake::YardocTask.new
|
data/benchmarks/lexer.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'ruby_ami'
|
6
|
+
require 'benchmark/ips'
|
7
|
+
|
8
|
+
class LexerHost
|
9
|
+
def initialize
|
10
|
+
@lexer = RubyAMI::Lexer.new self
|
11
|
+
end
|
12
|
+
|
13
|
+
def receive_data(data)
|
14
|
+
@lexer << data
|
15
|
+
end
|
16
|
+
|
17
|
+
def message_received(message)
|
18
|
+
end
|
19
|
+
|
20
|
+
def error_received(error)
|
21
|
+
end
|
22
|
+
|
23
|
+
def syntax_error_encountered(ignored_chunk)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
lexer_host = LexerHost.new
|
28
|
+
|
29
|
+
event = <<-EVENT
|
30
|
+
Event: Dial
|
31
|
+
SubEvent: <value>
|
32
|
+
Channel: <value>
|
33
|
+
Destination: <value>
|
34
|
+
CallerIDNum: <value>
|
35
|
+
CallerIDName: <value>
|
36
|
+
ConnectedLineNum: <value>
|
37
|
+
ConnectedLineName: <value>
|
38
|
+
UniqueID: <value>
|
39
|
+
DestUniqueID: <value>
|
40
|
+
Dialstring: <value>
|
41
|
+
|
42
|
+
EVENT
|
43
|
+
event.gsub!("\n", "\r\n")
|
44
|
+
|
45
|
+
Benchmark.ips do |ips|
|
46
|
+
ips.report("event lexing") { lexer_host.receive_data event }
|
47
|
+
end
|
data/features/lexer.feature
CHANGED
@@ -7,8 +7,6 @@ Feature: Lexing AMI
|
|
7
7
|
Given a new lexer
|
8
8
|
And a version header for AMI 1.0
|
9
9
|
|
10
|
-
When the buffer is lexed
|
11
|
-
|
12
10
|
Then the protocol should have lexed without syntax errors
|
13
11
|
And the version should be set to 1.0
|
14
12
|
|
@@ -17,28 +15,22 @@ Feature: Lexing AMI
|
|
17
15
|
And a version header for AMI 1.0
|
18
16
|
And a normal login success with events
|
19
17
|
|
20
|
-
When the buffer is lexed
|
21
|
-
|
22
18
|
Then the protocol should have lexed without syntax errors
|
23
19
|
And 1 message should have been received
|
24
20
|
|
25
21
|
Scenario: Lexing the initial AMI header and then a Response:Follows section
|
26
22
|
Given a new lexer
|
27
23
|
And a version header for AMI 1.0
|
28
|
-
And a multi-line Response:Follows body of
|
29
|
-
|
30
|
-
When the buffer is lexed
|
24
|
+
And a multi-line Response:Follows body of show_channels_from_wayne
|
31
25
|
|
32
26
|
Then the protocol should have lexed without syntax errors
|
33
|
-
And the 'follows' body of 1 message received should equal
|
27
|
+
And the 'follows' body of 1 message received should equal show_channels_from_wayne
|
34
28
|
|
35
29
|
Scenario: Lexing a Response:Follows section with no body
|
36
30
|
Given a new lexer
|
37
31
|
And a version header for AMI 1.0
|
38
32
|
And a multi-line Response:Follows body of empty_String
|
39
33
|
|
40
|
-
When the buffer is lexed
|
41
|
-
|
42
34
|
Then the protocol should have lexed without syntax errors
|
43
35
|
And the 'follows' body of 1 message received should equal empty_string
|
44
36
|
|
@@ -47,8 +39,6 @@ Feature: Lexing AMI
|
|
47
39
|
And a version header for AMI 1.0
|
48
40
|
Given a multi-line Response:Follows body of show_channels_from_wayne
|
49
41
|
|
50
|
-
When the buffer is lexed
|
51
|
-
|
52
42
|
Then the protocol should have lexed without syntax errors
|
53
43
|
And the 'follows' body of 1 message received should equal show_channels_from_wayne
|
54
44
|
|
@@ -57,8 +47,6 @@ Feature: Lexing AMI
|
|
57
47
|
And a version header for AMI 1.0
|
58
48
|
Given a multi-line Response:Follows response simulating uptime
|
59
49
|
|
60
|
-
When the buffer is lexed
|
61
|
-
|
62
50
|
Then the protocol should have lexed without syntax errors
|
63
51
|
And the first message received should have a key "System uptime" with value "46 minutes, 30 seconds"
|
64
52
|
|
@@ -66,8 +54,6 @@ Feature: Lexing AMI
|
|
66
54
|
Given a new lexer
|
67
55
|
And a multi-line Response:Follows body of with_colon_after_first_line
|
68
56
|
|
69
|
-
When the buffer is lexed
|
70
|
-
|
71
57
|
Then the protocol should have lexed without syntax errors
|
72
58
|
And 1 message should have been received
|
73
59
|
And the 'follows' body of 1 message received should equal with_colon_after_first_line
|
@@ -77,8 +63,6 @@ Feature: Lexing AMI
|
|
77
63
|
Given a new lexer
|
78
64
|
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
65
|
|
80
|
-
When the buffer is lexed
|
81
|
-
|
82
66
|
Then the protocol should have lexed without syntax errors
|
83
67
|
And 1 message should have been received
|
84
68
|
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"
|
@@ -88,27 +72,21 @@ Feature: Lexing AMI
|
|
88
72
|
And a version header for AMI 1.0
|
89
73
|
And an Authentication Required error
|
90
74
|
|
91
|
-
When the buffer is lexed
|
92
|
-
|
93
75
|
Then the protocol should have lexed without syntax errors
|
94
76
|
|
95
77
|
Scenario: Lexing the initial AMI header and then a Response:Follows section
|
96
78
|
Given a new lexer
|
97
79
|
And a version header for AMI 1.0
|
98
|
-
And a multi-line Response:Follows body of
|
99
|
-
And a multi-line Response:Follows body of
|
100
|
-
|
101
|
-
When the buffer is lexed
|
80
|
+
And a multi-line Response:Follows body of show_channels_from_wayne
|
81
|
+
And a multi-line Response:Follows body of show_channels_from_wayne
|
102
82
|
|
103
83
|
Then the protocol should have lexed without syntax errors
|
104
|
-
And the 'follows' body of 2 messages received should equal
|
84
|
+
And the 'follows' body of 2 messages received should equal show_channels_from_wayne
|
105
85
|
|
106
86
|
Scenario: Lexing a stanza without receiving an AMI header
|
107
87
|
Given a new lexer
|
108
88
|
And a normal login success with events
|
109
89
|
|
110
|
-
When the buffer is lexed
|
111
|
-
|
112
90
|
Then the protocol should have lexed without syntax errors
|
113
91
|
And 1 message should have been received
|
114
92
|
|
@@ -116,8 +94,6 @@ Feature: Lexing AMI
|
|
116
94
|
Given a new lexer
|
117
95
|
And an immediate response with text "Immediate responses are so ridiculous"
|
118
96
|
|
119
|
-
When the buffer is lexed
|
120
|
-
|
121
97
|
Then the protocol should have lexed without syntax errors
|
122
98
|
And 1 message should have been received
|
123
99
|
And 1 message should be an immediate response with text "Immediate responses are so ridiculous"
|
@@ -128,8 +104,6 @@ Feature: Lexing AMI
|
|
128
104
|
And an immediate response with text "No queues have been created."
|
129
105
|
And a normal login success with events
|
130
106
|
|
131
|
-
When the buffer is lexed
|
132
|
-
|
133
107
|
Then the protocol should have lexed without syntax errors
|
134
108
|
And 3 messages should have been received
|
135
109
|
And 1 message should be an immediate response with text "No queues have been created."
|
@@ -140,8 +114,6 @@ Feature: Lexing AMI
|
|
140
114
|
And a normal login success with events
|
141
115
|
And a Pong response with an ActionID of randomness
|
142
116
|
|
143
|
-
When the buffer is lexed
|
144
|
-
|
145
117
|
Then the protocol should have lexed without syntax errors
|
146
118
|
And 2 messages should have been received
|
147
119
|
|
@@ -150,8 +122,6 @@ Feature: Lexing AMI
|
|
150
122
|
And 5 Pong responses without an ActionID
|
151
123
|
And 5 Pong responses with an ActionID of randomness
|
152
124
|
|
153
|
-
When the buffer is lexed
|
154
|
-
|
155
125
|
Then the protocol should have lexed without syntax errors
|
156
126
|
And 10 messages should have been received
|
157
127
|
|
@@ -159,8 +129,6 @@ Feature: Lexing AMI
|
|
159
129
|
Given a new lexer
|
160
130
|
And a Pong response with an ActionID of 1224469850.61673
|
161
131
|
|
162
|
-
When the buffer is lexed
|
163
|
-
|
164
132
|
Then the first message received should have a key "ActionID" with value "1224469850.61673"
|
165
133
|
|
166
134
|
Scenario: A response containing a floating point value
|
@@ -170,7 +138,6 @@ Feature: Lexing AMI
|
|
170
138
|
And the custom stanza named "call" has key "Uniqueid" with value "1173223225.10309"
|
171
139
|
|
172
140
|
When the custom stanza named "call" is added to the buffer
|
173
|
-
And the buffer is lexed
|
174
141
|
|
175
142
|
Then the 1st message received should have a key "Uniqueid" with value "1173223225.10309"
|
176
143
|
|
@@ -186,7 +153,6 @@ Feature: Lexing AMI
|
|
186
153
|
And the custom stanza named "person" has key "I have spaces" with value "i have trailing padding "
|
187
154
|
|
188
155
|
When the custom stanza named "person" is added to the buffer
|
189
|
-
And the buffer is lexed
|
190
156
|
|
191
157
|
Then the protocol should have lexed without syntax errors
|
192
158
|
And the first message received should have a key "Name" with value "Jay Phillips"
|
@@ -202,8 +168,6 @@ Feature: Lexing AMI
|
|
202
168
|
Given a new lexer
|
203
169
|
And a normal login success with events split into two pieces
|
204
170
|
|
205
|
-
When the buffer is lexed
|
206
|
-
|
207
171
|
Then the protocol should have lexed without syntax errors
|
208
172
|
And 1 message should have been received
|
209
173
|
|
@@ -212,8 +176,6 @@ Feature: Lexing AMI
|
|
212
176
|
And an AMI error whose message is "Missing action in request"
|
213
177
|
And a normal login success with events
|
214
178
|
|
215
|
-
When the buffer is lexed
|
216
|
-
|
217
179
|
Then the protocol should have lexed without syntax errors
|
218
180
|
And 1 AMI error should have been received
|
219
181
|
And the 1st AMI error should have the message "Missing action in request"
|
@@ -225,8 +187,6 @@ Feature: Lexing AMI
|
|
225
187
|
And an immediate response with text "Yes, plain English is sent sometimes over AMI."
|
226
188
|
And a normal login success with events
|
227
189
|
|
228
|
-
When the buffer is lexed
|
229
|
-
|
230
190
|
Then the protocol should have lexed without syntax errors
|
231
191
|
And 3 messages should have been received
|
232
192
|
And 1 message should be an immediate response with text "Yes, plain English is sent sometimes over AMI."
|
@@ -239,7 +199,6 @@ Feature: Lexing AMI
|
|
239
199
|
And a custom header for event identified by "this_event" whose key is "AppData" and value is "agi://localhost"
|
240
200
|
|
241
201
|
When the custom event identified by "this_event" is added to the buffer
|
242
|
-
And the buffer is lexed
|
243
202
|
|
244
203
|
Then the protocol should have lexed without syntax errors
|
245
204
|
And 1 event should have been received
|
@@ -253,8 +212,6 @@ Feature: Lexing AMI
|
|
253
212
|
And syntactically invalid immediate_packet_with_colon
|
254
213
|
And a stanza break
|
255
214
|
|
256
|
-
When the buffer is lexed
|
257
|
-
|
258
215
|
Then 0 messages should have been received
|
259
216
|
And the protocol should have lexed with 1 syntax error
|
260
217
|
And the syntax error fixture named immediate_packet_with_colon should have been encountered
|
@@ -123,10 +123,6 @@ When 'the custom event identified by "$identifier" is added to the buffer' do |i
|
|
123
123
|
@lexer << stringified_event
|
124
124
|
end
|
125
125
|
|
126
|
-
When "the buffer is lexed" do
|
127
|
-
@lexer.resume!
|
128
|
-
end
|
129
|
-
|
130
126
|
########################################
|
131
127
|
#### THEN
|
132
128
|
########################################
|
@@ -160,7 +156,7 @@ Then /^the 'follows' body of (\d+) messages? received should equal (\w+)$/ do |n
|
|
160
156
|
end
|
161
157
|
|
162
158
|
Then "the version should be set to $version" do |version|
|
163
|
-
@lexer.ami_version.should eql(version
|
159
|
+
@lexer.ami_version.should eql(version)
|
164
160
|
end
|
165
161
|
|
166
162
|
Then /^the ([\w\d]*) message received should have a key "([^\"]*)" with value "([^\"]*)"$/ do |ordered,key,value|
|
@@ -93,11 +93,6 @@ end
|
|
93
93
|
|
94
94
|
def follows_body_text(name)
|
95
95
|
case name
|
96
|
-
when "ragel_description"
|
97
|
-
"Ragel is a software development tool that allows user actions to
|
98
|
-
be embedded into the transitions of a regular expression's corresponding state machine,
|
99
|
-
eliminating the need to switch from the regular expression engine and user code execution
|
100
|
-
environment and back again."
|
101
96
|
when "with_colon_after_first_line"
|
102
97
|
"Host Username Refresh State Reg.Time \r\nlax.teliax.net:5060 jicksta 105 Registered Tue, 11 Nov 2008 02:29:55"
|
103
98
|
when "show_channels_from_wayne"
|
@@ -4,7 +4,7 @@ module RubyAMI
|
|
4
4
|
class AGIResultParser
|
5
5
|
attr_reader :code, :result, :data
|
6
6
|
|
7
|
-
FORMAT = /^(?<code>\d{3}) result=(?<result>-?\d*) ?(?<data>\(?.*\)?)?$/.freeze
|
7
|
+
FORMAT = /^(?<code>\d{3})( result=(?<result>-?\d*))? ?(?<data>\(?.*\)?)?$/.freeze
|
8
8
|
DATA_KV_FORMAT = /(?<key>[\w\d]+)=(?<value>[\w\d]*)/.freeze
|
9
9
|
DATA_CLEANER = /(^\()|(\)$)/.freeze
|
10
10
|
|
@@ -31,7 +31,7 @@ module RubyAMI
|
|
31
31
|
|
32
32
|
def parse
|
33
33
|
@code = match[:code].to_i
|
34
|
-
@result = match[:result].to_i
|
34
|
+
@result = match[:result] ? match[:result].to_i : nil
|
35
35
|
@data = match[:data] ? match[:data].gsub(DATA_CLEANER, '').freeze : nil
|
36
36
|
end
|
37
37
|
|
data/lib/ruby_ami/error.rb
CHANGED
@@ -0,0 +1,148 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module RubyAMI
|
4
|
+
class Lexer
|
5
|
+
STANZA_BREAK = "\r\n\r\n"
|
6
|
+
PROMPT = /Asterisk Call Manager\/(\d+\.\d+)\r\n/
|
7
|
+
KEYVALUEPAIR = /^([[[:alnum:]]-_ ]+): *(.*)\r\n/
|
8
|
+
FOLLOWSDELIMITER = /\r?\n?--END COMMAND--\r\n\r\n/
|
9
|
+
SUCCESS = /response: *success/i
|
10
|
+
PONG = /response: *pong/i
|
11
|
+
EVENT = /event: *(?<event_name>.*)?/i
|
12
|
+
ERROR = /response: *error/i
|
13
|
+
FOLLOWS = /response: *follows/i
|
14
|
+
SCANNER = /.*?#{STANZA_BREAK}/m
|
15
|
+
HEADER_SLICE = /.*\r\n/
|
16
|
+
IMMEDIATE_RESP = /.*/
|
17
|
+
CLASSIFIER = /((?<event>#{EVENT})|(?<success>#{SUCCESS})|(?<pong>#{PONG})|(?<follows>#{FOLLOWS})|(?<error>#{ERROR})|(?<immediate>#{IMMEDIATE_RESP})\r\n)\r\n/i
|
18
|
+
|
19
|
+
attr_accessor :ami_version
|
20
|
+
|
21
|
+
def initialize(delegate = nil)
|
22
|
+
@delegate = delegate
|
23
|
+
@buffer = ""
|
24
|
+
@ami_version = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(new_data)
|
28
|
+
@buffer << new_data
|
29
|
+
parse_buffer
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parse_buffer
|
35
|
+
# Special case for the protocol header
|
36
|
+
if @buffer =~ PROMPT
|
37
|
+
@ami_version = $1
|
38
|
+
@buffer.slice! HEADER_SLICE
|
39
|
+
end
|
40
|
+
|
41
|
+
# We need at least one complete message before parsing
|
42
|
+
return unless @buffer.include?(STANZA_BREAK)
|
43
|
+
|
44
|
+
@processed = 0
|
45
|
+
|
46
|
+
response_follows_message = false
|
47
|
+
current_message = nil
|
48
|
+
@buffer.scan(SCANNER).each do |raw|
|
49
|
+
if response_follows_message
|
50
|
+
if handle_response_follows(response_follows_message, raw)
|
51
|
+
@processed += raw.length
|
52
|
+
message_received response_follows_message
|
53
|
+
response_follows_message = nil
|
54
|
+
end
|
55
|
+
else
|
56
|
+
response_follows_message = parse_message raw
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@buffer.slice! 0, @processed
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_message(raw)
|
63
|
+
return if raw.length == 0
|
64
|
+
|
65
|
+
# Mark this message as processed, including the 4 stripped cr/lf bytes
|
66
|
+
@processed += raw.length
|
67
|
+
|
68
|
+
match = raw.match CLASSIFIER
|
69
|
+
|
70
|
+
msg = if match[:event]
|
71
|
+
Event.new match[:event_name]
|
72
|
+
elsif match[:success] || match[:pong]
|
73
|
+
Response.new
|
74
|
+
elsif match[:follows]
|
75
|
+
response_follows = true
|
76
|
+
Response.new
|
77
|
+
elsif match[:error]
|
78
|
+
Error.new
|
79
|
+
elsif match[:immediate]
|
80
|
+
if raw.include?(':')
|
81
|
+
syntax_error_encountered raw.chomp(STANZA_BREAK)
|
82
|
+
return
|
83
|
+
end
|
84
|
+
immediate_response = true
|
85
|
+
Response.from_immediate_response match[:immediate]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Strip off the header line
|
89
|
+
raw.slice! HEADER_SLICE
|
90
|
+
populate_message_body msg, raw
|
91
|
+
|
92
|
+
return msg if response_follows && !handle_response_follows(msg, raw)
|
93
|
+
|
94
|
+
case msg
|
95
|
+
when Error
|
96
|
+
error_received msg
|
97
|
+
else
|
98
|
+
message_received msg
|
99
|
+
end
|
100
|
+
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Called after a response or event has been successfully parsed.
|
106
|
+
#
|
107
|
+
# @param [Response, Event] message The message just received
|
108
|
+
#
|
109
|
+
def message_received(message)
|
110
|
+
@delegate.message_received message
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Called after an AMI error has been successfully parsed.
|
115
|
+
#
|
116
|
+
# @param [Response, Event] message The message just received
|
117
|
+
#
|
118
|
+
def error_received(message)
|
119
|
+
@delegate.error_received message
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Called when there's a syntax error on the socket. This doesn't happen as often as it should because, in many cases,
|
124
|
+
# it's impossible to distinguish between a syntax error and an immediate packet.
|
125
|
+
#
|
126
|
+
# @param [String] ignored_chunk The offending text which caused the syntax error.
|
127
|
+
def syntax_error_encountered(ignored_chunk)
|
128
|
+
@delegate.syntax_error_encountered ignored_chunk
|
129
|
+
end
|
130
|
+
|
131
|
+
def populate_message_body(obj, raw)
|
132
|
+
while raw.slice! KEYVALUEPAIR
|
133
|
+
obj[$1] = $2
|
134
|
+
end
|
135
|
+
obj
|
136
|
+
end
|
137
|
+
|
138
|
+
def handle_response_follows(obj, raw)
|
139
|
+
obj.text_body ||= ''
|
140
|
+
obj.text_body << raw
|
141
|
+
return false unless raw =~ FOLLOWSDELIMITER
|
142
|
+
obj.text_body.sub! FOLLOWSDELIMITER, ''
|
143
|
+
obj.text_body.chomp!
|
144
|
+
true
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
data/lib/ruby_ami/version.rb
CHANGED
data/ruby_ami.gemspec
CHANGED
@@ -5,11 +5,11 @@ require "ruby_ami/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "ruby_ami"
|
7
7
|
s.version = RubyAMI::VERSION
|
8
|
-
s.authors = ["Ben Langfeld"]
|
9
|
-
s.email = ["ben@langfeld.me"]
|
8
|
+
s.authors = ["Ben Langfeld", "Ben Klang"]
|
9
|
+
s.email = ["ben@langfeld.me", "bklang@mojolingo.com"]
|
10
10
|
s.homepage = ""
|
11
11
|
s.summary = %q{Futzing with AMI so you don't have to}
|
12
|
-
s.description = %q{A Ruby client library for the Asterisk Management Interface
|
12
|
+
s.description = %q{A Ruby client library for the Asterisk Management Interface built on Celluloid IO.}
|
13
13
|
|
14
14
|
s.rubyforge_project = "ruby_ami"
|
15
15
|
|
@@ -27,5 +27,7 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.add_development_dependency %q<rake>, [">= 0"]
|
28
28
|
s.add_development_dependency %q<guard-rspec>
|
29
29
|
s.add_development_dependency %q<guard-shell>
|
30
|
-
s.add_development_dependency %q<
|
30
|
+
s.add_development_dependency %q<guard-cucumber>
|
31
|
+
s.add_development_dependency %q<guard-rake>
|
32
|
+
s.add_development_dependency %q<benchmark_suite>
|
31
33
|
end
|
@@ -48,5 +48,14 @@ module RubyAMI
|
|
48
48
|
its(:data) { should == 'foo=bar' }
|
49
49
|
its(:data_hash) { should == {'foo' => 'bar'} }
|
50
50
|
end
|
51
|
+
|
52
|
+
context 'with a 5xx error' do
|
53
|
+
let(:result_string) { "510%20Invalid%20or%20unknown%20command%0A" }
|
54
|
+
|
55
|
+
its(:code) { should == 510 }
|
56
|
+
its(:result) { should be_nil }
|
57
|
+
its(:data) { should == 'Invalid or unknown command' }
|
58
|
+
its(:data_hash) { should be_nil }
|
59
|
+
end
|
51
60
|
end
|
52
61
|
end
|
@@ -98,6 +98,32 @@ Message: Recording started
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
+
it "can process an action with a Response: Follows result" do
|
102
|
+
action_id = RubyAMI.new_uuid
|
103
|
+
response = nil
|
104
|
+
mocked_server(1, lambda { response = @stream.send_action('Command', 'Command' => 'dialplan add extension 1,1,AGI,agi:async into adhearsion-redirect') }) do |val, server|
|
105
|
+
val.should == <<-ACTION
|
106
|
+
Action: command\r
|
107
|
+
ActionID: #{action_id}\r
|
108
|
+
Command: dialplan add extension 1,1,AGI,agi:async into adhearsion-redirect\r
|
109
|
+
\r
|
110
|
+
ACTION
|
111
|
+
|
112
|
+
server.send_data <<-EVENT
|
113
|
+
Response: Follows
|
114
|
+
Privilege: Command
|
115
|
+
ActionID: #{action_id}
|
116
|
+
Extension '1,1,AGI(agi:async)' added into 'adhearsion-redirect' context
|
117
|
+
--END COMMAND--
|
118
|
+
|
119
|
+
EVENT
|
120
|
+
end
|
121
|
+
|
122
|
+
expected_response = Response.new 'Privilege' => 'Command', 'ActionID' => action_id
|
123
|
+
expected_response.text_body = %q{Extension '1,1,AGI(agi:async)' added into 'adhearsion-redirect' context}
|
124
|
+
response.should == expected_response
|
125
|
+
end
|
126
|
+
|
101
127
|
context "with a username and password set" do
|
102
128
|
let(:username) { 'fred' }
|
103
129
|
let(:password) { 'jones' }
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_ami
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Langfeld
|
8
|
+
- Ben Klang
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2013-
|
12
|
+
date: 2013-05-29 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: celluloid-io
|
@@ -123,7 +124,7 @@ dependencies:
|
|
123
124
|
- !ruby/object:Gem::Version
|
124
125
|
version: '0'
|
125
126
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
127
|
+
name: guard-cucumber
|
127
128
|
requirement: !ruby/object:Gem::Requirement
|
128
129
|
requirements:
|
129
130
|
- - '>='
|
@@ -136,10 +137,39 @@ dependencies:
|
|
136
137
|
- - '>='
|
137
138
|
- !ruby/object:Gem::Version
|
138
139
|
version: '0'
|
139
|
-
|
140
|
-
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: guard-rake
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: benchmark_suite
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
type: :development
|
162
|
+
prerelease: false
|
163
|
+
version_requirements: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - '>='
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
description: A Ruby client library for the Asterisk Management Interface built on
|
169
|
+
Celluloid IO.
|
141
170
|
email:
|
142
171
|
- ben@langfeld.me
|
172
|
+
- bklang@mojolingo.com
|
143
173
|
executables: []
|
144
174
|
extensions: []
|
145
175
|
extra_rdoc_files: []
|
@@ -153,6 +183,7 @@ files:
|
|
153
183
|
- LICENSE.txt
|
154
184
|
- README.md
|
155
185
|
- Rakefile
|
186
|
+
- benchmarks/lexer.rb
|
156
187
|
- cucumber.yml
|
157
188
|
- features/lexer.feature
|
158
189
|
- features/step_definitions/lexer_steps.rb
|
@@ -168,8 +199,7 @@ files:
|
|
168
199
|
- lib/ruby_ami/core_ext/celluloid.rb
|
169
200
|
- lib/ruby_ami/error.rb
|
170
201
|
- lib/ruby_ami/event.rb
|
171
|
-
- lib/ruby_ami/lexer.
|
172
|
-
- lib/ruby_ami/lexer_machine.rl
|
202
|
+
- lib/ruby_ami/lexer.rb
|
173
203
|
- lib/ruby_ami/response.rb
|
174
204
|
- lib/ruby_ami/stream.rb
|
175
205
|
- lib/ruby_ami/version.rb
|
@@ -184,7 +214,6 @@ files:
|
|
184
214
|
- spec/ruby_ami/stream_spec.rb
|
185
215
|
- spec/spec_helper.rb
|
186
216
|
- spec/support/mock_server.rb
|
187
|
-
- lib/ruby_ami/lexer.rb
|
188
217
|
homepage: ''
|
189
218
|
licenses: []
|
190
219
|
metadata: {}
|
@@ -204,7 +233,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
233
|
version: '0'
|
205
234
|
requirements: []
|
206
235
|
rubyforge_project: ruby_ami
|
207
|
-
rubygems_version: 2.0.
|
236
|
+
rubygems_version: 2.0.3
|
208
237
|
signing_key:
|
209
238
|
specification_version: 4
|
210
239
|
summary: Futzing with AMI so you don't have to
|
data/lib/ruby_ami/lexer.rl.rb
DELETED
@@ -1,304 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module RubyAMI
|
3
|
-
class Lexer
|
4
|
-
|
5
|
-
KILOBYTE = 1024
|
6
|
-
BUFFER_SIZE = 128 * KILOBYTE unless defined? BUFFER_SIZE
|
7
|
-
|
8
|
-
##
|
9
|
-
# IMPORTANT! See method documentation for adjust_pointers!
|
10
|
-
#
|
11
|
-
# @see adjust_pointers
|
12
|
-
#
|
13
|
-
POINTERS = [
|
14
|
-
:@current_pointer,
|
15
|
-
:@token_start,
|
16
|
-
:@token_end,
|
17
|
-
:@version_start,
|
18
|
-
:@event_name_start,
|
19
|
-
:@current_key_position,
|
20
|
-
:@current_value_position,
|
21
|
-
:@last_seen_value_end,
|
22
|
-
:@error_reason_start,
|
23
|
-
:@follows_text_start,
|
24
|
-
:@current_syntax_error_start,
|
25
|
-
:@immediate_response_start
|
26
|
-
]
|
27
|
-
|
28
|
-
%%{
|
29
|
-
machine ami_protocol_parser;
|
30
|
-
|
31
|
-
# All required Ragel actions are implemented as Ruby methods.
|
32
|
-
|
33
|
-
# Executed after a "Response: Success" or "Response: Pong"
|
34
|
-
action init_success { init_success }
|
35
|
-
|
36
|
-
action init_response_follows { init_response_follows }
|
37
|
-
|
38
|
-
action init_error { init_error }
|
39
|
-
|
40
|
-
action message_received { message_received @current_message }
|
41
|
-
action error_received { error_received @current_message }
|
42
|
-
|
43
|
-
action version_starts { version_starts }
|
44
|
-
action version_stops { version_stops }
|
45
|
-
|
46
|
-
action key_starts { key_starts }
|
47
|
-
action key_stops { key_stops }
|
48
|
-
|
49
|
-
action value_starts { value_starts }
|
50
|
-
action value_stops { value_stops }
|
51
|
-
|
52
|
-
action error_reason_starts { error_reason_starts }
|
53
|
-
action error_reason_stops { error_reason_stops }
|
54
|
-
|
55
|
-
action syntax_error_starts { syntax_error_starts }
|
56
|
-
action syntax_error_stops { syntax_error_stops }
|
57
|
-
|
58
|
-
action immediate_response_starts { immediate_response_starts }
|
59
|
-
action immediate_response_stops { immediate_response_stops }
|
60
|
-
|
61
|
-
action follows_text_starts { follows_text_starts }
|
62
|
-
action follows_text_stops { follows_text_stops }
|
63
|
-
|
64
|
-
action event_name_starts { event_name_starts }
|
65
|
-
action event_name_stops { event_name_stops }
|
66
|
-
|
67
|
-
include ami_protocol_parser_machine "lexer_machine.rl";
|
68
|
-
|
69
|
-
}%%##
|
70
|
-
|
71
|
-
attr_accessor :ami_version
|
72
|
-
|
73
|
-
def initialize(delegate = nil)
|
74
|
-
@delegate = delegate
|
75
|
-
@data = ''.force_encoding('ISO-8859-1')
|
76
|
-
@current_pointer = 0
|
77
|
-
@ragel_stack = []
|
78
|
-
@ami_version = 0.0
|
79
|
-
|
80
|
-
%%{
|
81
|
-
# All other variables become local, letting Ruby garbage collect them. This
|
82
|
-
# prevents us from having to manually reset them.
|
83
|
-
|
84
|
-
variable data @data;
|
85
|
-
variable p @current_pointer;
|
86
|
-
variable pe @data_ending_pointer;
|
87
|
-
variable cs @current_state;
|
88
|
-
variable ts @token_start;
|
89
|
-
variable te @token_end;
|
90
|
-
variable act @ragel_act;
|
91
|
-
variable eof @eof;
|
92
|
-
variable stack @ragel_stack;
|
93
|
-
variable top @ragel_stack_top;
|
94
|
-
|
95
|
-
write data;
|
96
|
-
write init;
|
97
|
-
}%%##
|
98
|
-
end
|
99
|
-
|
100
|
-
def <<(new_data)
|
101
|
-
extend_buffer_with new_data
|
102
|
-
resume!
|
103
|
-
end
|
104
|
-
|
105
|
-
def resume!
|
106
|
-
%%{ write exec; }%%##
|
107
|
-
end
|
108
|
-
|
109
|
-
def extend_buffer_with(new_data)
|
110
|
-
length = new_data.size
|
111
|
-
|
112
|
-
if length > BUFFER_SIZE
|
113
|
-
raise Exception, "ERROR: Buffer overrun! Input size (#{new_data.size}) larger than buffer (#{BUFFER_SIZE})"
|
114
|
-
end
|
115
|
-
|
116
|
-
if length + @data.size > BUFFER_SIZE
|
117
|
-
if @data.size != @current_pointer
|
118
|
-
if @current_pointer < length
|
119
|
-
# We are about to shift more bytes off the array than we have
|
120
|
-
# parsed. This will cause the parser to lose state so
|
121
|
-
# integrity cannot be guaranteed.
|
122
|
-
raise Exception, "ERROR: Buffer overrun! AMI parser cannot guarantee sanity. New data size: #{new_data.size}; Current pointer at #{@current_pointer}; Data size: #{@data.size}"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
@data.slice! 0...length
|
126
|
-
adjust_pointers -length
|
127
|
-
end
|
128
|
-
@data << new_data.force_encoding('ISO-8859-1')
|
129
|
-
@data_ending_pointer = @data.size
|
130
|
-
end
|
131
|
-
|
132
|
-
protected
|
133
|
-
|
134
|
-
##
|
135
|
-
# This method will adjust all pointers into the buffer according
|
136
|
-
# to the supplied offset. This is necessary any time the buffer
|
137
|
-
# changes, for example when the sliding window is incremented forward
|
138
|
-
# after new data is received.
|
139
|
-
#
|
140
|
-
# It is VERY IMPORTANT that when any additional pointers are defined
|
141
|
-
# that they are added to this method. Unpredictable results may
|
142
|
-
# otherwise occur!
|
143
|
-
#
|
144
|
-
# @see https://adhearsion.lighthouseapp.com/projects/5871-adhearsion/tickets/72-ami-lexer-buffer-offset#ticket-72-26
|
145
|
-
#
|
146
|
-
# @param offset Adjust pointers by offset. May be negative.
|
147
|
-
#
|
148
|
-
def adjust_pointers(offset)
|
149
|
-
POINTERS.each do |ptr|
|
150
|
-
value = instance_variable_get(ptr)
|
151
|
-
instance_variable_set(ptr, value + offset) if !value.nil?
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
##
|
156
|
-
# Called after a response or event has been successfully parsed.
|
157
|
-
#
|
158
|
-
# @param [Response, Event] message The message just received
|
159
|
-
#
|
160
|
-
def message_received(message)
|
161
|
-
@delegate.message_received message
|
162
|
-
end
|
163
|
-
|
164
|
-
##
|
165
|
-
# Called when there is an Error: stanza on the socket. Could be caused by executing an unrecognized command, trying
|
166
|
-
# to originate into an invalid priority, etc. Note: many errors' responses are actually tightly coupled to a
|
167
|
-
# Event which comes directly after it. Often the message will say something like "Channel status
|
168
|
-
# will follow".
|
169
|
-
#
|
170
|
-
# @param [String] reason The reason given in the Message: header for the error stanza.
|
171
|
-
#
|
172
|
-
def error_received(message)
|
173
|
-
@delegate.error_received message
|
174
|
-
end
|
175
|
-
|
176
|
-
##
|
177
|
-
# Called when there's a syntax error on the socket. This doesn't happen as often as it should because, in many cases,
|
178
|
-
# it's impossible to distinguish between a syntax error and an immediate packet.
|
179
|
-
#
|
180
|
-
# @param [String] ignored_chunk The offending text which caused the syntax error.
|
181
|
-
def syntax_error_encountered(ignored_chunk)
|
182
|
-
@delegate.syntax_error_encountered ignored_chunk
|
183
|
-
end
|
184
|
-
|
185
|
-
def init_success
|
186
|
-
@current_message = Response.new
|
187
|
-
end
|
188
|
-
|
189
|
-
def init_response_follows
|
190
|
-
@current_message = Response.new
|
191
|
-
end
|
192
|
-
|
193
|
-
def init_error
|
194
|
-
@current_message = Error.new
|
195
|
-
end
|
196
|
-
|
197
|
-
def version_starts
|
198
|
-
@version_start = @current_pointer
|
199
|
-
end
|
200
|
-
|
201
|
-
def version_stops
|
202
|
-
self.ami_version = @data[@version_start...@current_pointer].to_f
|
203
|
-
@version_start = nil
|
204
|
-
end
|
205
|
-
|
206
|
-
def event_name_starts
|
207
|
-
@event_name_start = @current_pointer
|
208
|
-
end
|
209
|
-
|
210
|
-
def event_name_stops
|
211
|
-
event_name = @data[@event_name_start...@current_pointer]
|
212
|
-
@event_name_start = nil
|
213
|
-
@current_message = Event.new(event_name)
|
214
|
-
end
|
215
|
-
|
216
|
-
def key_starts
|
217
|
-
@current_key_position = @current_pointer
|
218
|
-
end
|
219
|
-
|
220
|
-
def key_stops
|
221
|
-
@current_key = @data[@current_key_position...@current_pointer]
|
222
|
-
end
|
223
|
-
|
224
|
-
def value_starts
|
225
|
-
@current_value_position = @current_pointer
|
226
|
-
end
|
227
|
-
|
228
|
-
def value_stops
|
229
|
-
@current_value = @data[@current_value_position...@current_pointer]
|
230
|
-
@last_seen_value_end = @current_pointer + 2 # 2 for \r\n
|
231
|
-
add_pair_to_current_message
|
232
|
-
end
|
233
|
-
|
234
|
-
def error_reason_starts
|
235
|
-
@error_reason_start = @current_pointer
|
236
|
-
end
|
237
|
-
|
238
|
-
def error_reason_stops
|
239
|
-
@current_message.message = @data[@error_reason_start...@current_pointer]
|
240
|
-
end
|
241
|
-
|
242
|
-
def follows_text_starts
|
243
|
-
@follows_text_start = @current_pointer
|
244
|
-
end
|
245
|
-
|
246
|
-
def follows_text_stops
|
247
|
-
text = @data[@last_seen_value_end..@current_pointer]
|
248
|
-
text.sub! /\r?\n--END COMMAND--/, ""
|
249
|
-
@current_message.text_body = text
|
250
|
-
@follows_text_start = nil
|
251
|
-
end
|
252
|
-
|
253
|
-
def add_pair_to_current_message
|
254
|
-
@current_message[@current_key] = @current_value
|
255
|
-
reset_key_and_value_positions
|
256
|
-
end
|
257
|
-
|
258
|
-
def reset_key_and_value_positions
|
259
|
-
@current_key, @current_value, @current_key_position, @current_value_position = nil
|
260
|
-
end
|
261
|
-
|
262
|
-
def syntax_error_starts
|
263
|
-
@current_syntax_error_start = @current_pointer # Adding 1 since the pointer is still set to the last successful match
|
264
|
-
end
|
265
|
-
|
266
|
-
def syntax_error_stops
|
267
|
-
# Subtracting 3 from @current_pointer below for "\r\n" which separates a stanza
|
268
|
-
offending_data = @data[@current_syntax_error_start...@current_pointer - 1]
|
269
|
-
syntax_error_encountered offending_data
|
270
|
-
@current_syntax_error_start = nil
|
271
|
-
end
|
272
|
-
|
273
|
-
def immediate_response_starts
|
274
|
-
@immediate_response_start = @current_pointer
|
275
|
-
end
|
276
|
-
|
277
|
-
def immediate_response_stops
|
278
|
-
message = @data[@immediate_response_start...(@current_pointer -1)]
|
279
|
-
message_received Response.from_immediate_response(message)
|
280
|
-
end
|
281
|
-
|
282
|
-
##
|
283
|
-
# This method is used primarily in debugging.
|
284
|
-
#
|
285
|
-
def view_buffer(message = nil)
|
286
|
-
message ||= "Viewing the buffer"
|
287
|
-
|
288
|
-
buffer = @data.clone
|
289
|
-
buffer.insert(@current_pointer, "\033[0;31m\033[1;31m^\033[0m")
|
290
|
-
|
291
|
-
buffer.gsub!("\r", "\\\\r")
|
292
|
-
buffer.gsub!("\n", "\\n\n")
|
293
|
-
|
294
|
-
puts <<-INSPECTION
|
295
|
-
VVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
296
|
-
#### #{message}
|
297
|
-
#############################
|
298
|
-
#{buffer}
|
299
|
-
#############################
|
300
|
-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
301
|
-
INSPECTION
|
302
|
-
end
|
303
|
-
end
|
304
|
-
end
|
@@ -1,87 +0,0 @@
|
|
1
|
-
%%{ #%
|
2
|
-
|
3
|
-
#########
|
4
|
-
## This file is written with the Ragel programming language and parses the Asterisk Manager Interface protocol. It depends
|
5
|
-
## upon Ragel actions which should be implemented in another Ragel-parsed file which includes this file.
|
6
|
-
##
|
7
|
-
## Ragel was used because the AMI protocol is extremely non-deterministic and, in the edge cases, requires something both
|
8
|
-
## very robust and something which can recover from syntax errors.
|
9
|
-
##
|
10
|
-
## Note: This file is language agnostic. From this AMI parsers in many other languages can be generated.
|
11
|
-
#########
|
12
|
-
|
13
|
-
machine ami_protocol_parser_machine;
|
14
|
-
|
15
|
-
cr = "\r"; # A carriage return. Used before (almost) every newline character.
|
16
|
-
lf = "\n"; # Newline. Used (with cr) to separate key/value pairs and stanzas.
|
17
|
-
crlf = cr lf; # Means "carriage return and line feed". Used to separate key/value pairs and stanzas
|
18
|
-
loose_newline = cr? lf; # Used sometimes when the AMI protocol is nondeterministic about the delimiter
|
19
|
-
|
20
|
-
white = [\t ]; # Single whitespace character, either a tab or a space
|
21
|
-
colon = ":" [ ]**; # Separates keys from values. "A colon followed by any number of spaces"
|
22
|
-
stanza_break = crlf crlf; # The seperator between two stanzas.
|
23
|
-
rest_of_line = (any* -- crlf); # Match all characters until the next line seperator.
|
24
|
-
|
25
|
-
Prompt = "Asterisk Call Manager/" digit+ >version_starts "." digit+ %version_stops crlf;
|
26
|
-
|
27
|
-
Key = ((alnum | print) -- (cr | lf | ":"))+;
|
28
|
-
KeyValuePair = Key >key_starts %key_stops colon rest_of_line >value_starts %value_stops crlf;
|
29
|
-
|
30
|
-
FollowsDelimiter = loose_newline "--END COMMAND--";
|
31
|
-
|
32
|
-
Response = "Response"i colon;
|
33
|
-
|
34
|
-
Success = Response "Success"i %init_success crlf @{ fgoto success; };
|
35
|
-
Pong = Response "Pong"i %init_success crlf @{ fgoto success; };
|
36
|
-
Event = "Event"i colon %event_name_starts rest_of_line %event_name_stops crlf @{ fgoto success; };
|
37
|
-
Error = Response "Error"i %init_error crlf (("Message"i colon rest_of_line >error_reason_starts crlf >error_reason_stops) | KeyValuePair)+ crlf @error_received;
|
38
|
-
Follows = Response "Follows"i crlf @init_response_follows @{ fgoto response_follows; };
|
39
|
-
|
40
|
-
# For "Response: Follows"
|
41
|
-
FollowsBody = (any* -- FollowsDelimiter) >follows_text_starts FollowsDelimiter @follows_text_stops crlf;
|
42
|
-
|
43
|
-
ImmediateResponse = (any+ -- (loose_newline | ":")) >immediate_response_starts loose_newline @immediate_response_stops @{fret;};
|
44
|
-
SyntaxError = (any+ -- crlf) >syntax_error_starts crlf @syntax_error_stops;
|
45
|
-
|
46
|
-
irregularity := |*
|
47
|
-
ImmediateResponse; # Performs the fret in the ImmediateResponse FSM
|
48
|
-
SyntaxError => { fret; };
|
49
|
-
*|;
|
50
|
-
|
51
|
-
# When a new socket is established, Asterisk will send the version of the protocol per the Prompt machine. Because it's
|
52
|
-
# tedious for unit tests to always send this, we'll put some intelligence into this parser to support going straight into
|
53
|
-
# the protocol-parsing machine. It's also conceivable that a variant of AMI would not send this initial information.
|
54
|
-
main := |*
|
55
|
-
Prompt => { fgoto protocol; };
|
56
|
-
any => {
|
57
|
-
# If this scanner's look-ahead capability didn't match the prompt, let's ignore the need for a prompt
|
58
|
-
fhold;
|
59
|
-
fgoto protocol;
|
60
|
-
};
|
61
|
-
*|;
|
62
|
-
|
63
|
-
protocol := |*
|
64
|
-
Prompt;
|
65
|
-
Success;
|
66
|
-
Pong;
|
67
|
-
Event;
|
68
|
-
Error;
|
69
|
-
Follows crlf;
|
70
|
-
crlf => { fgoto protocol; }; # If we get a crlf out of place, let's just ignore it.
|
71
|
-
any => {
|
72
|
-
# If NONE of the above patterns match, we consider this a syntax error. The irregularity machine can recover gracefully.
|
73
|
-
fhold;
|
74
|
-
fcall irregularity;
|
75
|
-
};
|
76
|
-
*|;
|
77
|
-
|
78
|
-
success := KeyValuePair* crlf @message_received @{fgoto protocol;};
|
79
|
-
|
80
|
-
# For the "Response: Follows" protocol abnormality. What happens if there's a protocol irregularity in this state???
|
81
|
-
response_follows := |*
|
82
|
-
KeyValuePair+;
|
83
|
-
FollowsBody;
|
84
|
-
crlf @{ message_received @current_message; fgoto protocol; };
|
85
|
-
*|;
|
86
|
-
|
87
|
-
}%%
|