jicksta-adhearsion 0.7.999
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +6 -0
- data/EVENTS +11 -0
- data/LICENSE +456 -0
- data/README.txt +5 -0
- data/Rakefile +120 -0
- data/adhearsion.gemspec +146 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +87 -0
- data/app_generators/ahn/templates/.ahnrc +34 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +23 -0
- data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
- data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
- data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
- data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
- data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
- data/app_generators/ahn/templates/config/startup.rb +53 -0
- data/app_generators/ahn/templates/dialplan.rb +3 -0
- data/app_generators/ahn/templates/events.rb +32 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +42 -0
- data/examples/asterisk_manager_interface/standalone.rb +51 -0
- data/lib/adhearsion/cli.rb +223 -0
- data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
- data/lib/adhearsion/component_manager.rb +208 -0
- data/lib/adhearsion/events_support.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +9 -0
- data/lib/adhearsion/foundation/blank_slate.rb +5 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/foundation/event_socket.rb +203 -0
- data/lib/adhearsion/foundation/future_resource.rb +36 -0
- data/lib/adhearsion/foundation/global.rb +1 -0
- data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
- data/lib/adhearsion/foundation/numeric.rb +13 -0
- data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
- data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
- data/lib/adhearsion/foundation/string.rb +26 -0
- data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
- data/lib/adhearsion/foundation/thread_safety.rb +7 -0
- data/lib/adhearsion/host_definitions.rb +67 -0
- data/lib/adhearsion/initializer/asterisk.rb +81 -0
- data/lib/adhearsion/initializer/configuration.rb +254 -0
- data/lib/adhearsion/initializer/database.rb +49 -0
- data/lib/adhearsion/initializer/drb.rb +31 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/initializer.rb +373 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/deprecations.rb +59 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/tasks.rb +16 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/call.rb +440 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +218 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/adhearsion.rb +37 -0
- data/lib/theatre/README.markdown +64 -0
- data/lib/theatre/callback_definition_loader.rb +84 -0
- data/lib/theatre/guid.rb +23 -0
- data/lib/theatre/invocation.rb +121 -0
- data/lib/theatre/namespace_manager.rb +153 -0
- data/lib/theatre/version.rb +2 -0
- data/lib/theatre.rb +151 -0
- metadata +177 -0
@@ -0,0 +1,286 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'ami_messages.rb')
|
2
|
+
|
3
|
+
module Adhearsion
|
4
|
+
module VoIP
|
5
|
+
module Asterisk
|
6
|
+
module Manager
|
7
|
+
class AbstractAsteriskManagerInterfaceStreamLexer
|
8
|
+
|
9
|
+
BUFFER_SIZE = 8.kilobytes unless defined? BUFFER_SIZE
|
10
|
+
|
11
|
+
%%{
|
12
|
+
machine ami_protocol_parser;
|
13
|
+
|
14
|
+
# All required Ragel actions are implemented as Ruby methods.
|
15
|
+
|
16
|
+
# Executed after a "Respone: Success" or "Response: Pong"
|
17
|
+
action init_success { init_success }
|
18
|
+
|
19
|
+
action init_response_follows { init_response_follows }
|
20
|
+
|
21
|
+
action init_error { init_error }
|
22
|
+
|
23
|
+
action message_received { message_received @current_message }
|
24
|
+
action error_received { error_received @current_message }
|
25
|
+
|
26
|
+
action version_starts { version_starts }
|
27
|
+
action version_stops { version_stops }
|
28
|
+
|
29
|
+
action key_starts { key_starts }
|
30
|
+
action key_stops { key_stops }
|
31
|
+
|
32
|
+
action value_starts { value_starts }
|
33
|
+
action value_stops { value_stops }
|
34
|
+
|
35
|
+
action error_reason_starts { error_reason_starts }
|
36
|
+
action error_reason_stops { error_reason_stops }
|
37
|
+
|
38
|
+
action syntax_error_starts { syntax_error_starts }
|
39
|
+
action syntax_error_stops { syntax_error_stops }
|
40
|
+
|
41
|
+
action immediate_response_starts { immediate_response_starts }
|
42
|
+
action immediate_response_stops { immediate_response_stops }
|
43
|
+
|
44
|
+
action follows_text_starts { follows_text_starts }
|
45
|
+
action follows_text_stops { follows_text_stops }
|
46
|
+
|
47
|
+
action event_name_starts { event_name_starts }
|
48
|
+
action event_name_stops { event_name_stops }
|
49
|
+
|
50
|
+
include ami_protocol_parser_machine "ami_protocol_lexer_machine.rl";
|
51
|
+
|
52
|
+
}%%##
|
53
|
+
|
54
|
+
attr_accessor(:ami_version)
|
55
|
+
def initialize
|
56
|
+
|
57
|
+
@data = ""
|
58
|
+
@current_pointer = 0
|
59
|
+
@ragel_stack = []
|
60
|
+
|
61
|
+
%%{
|
62
|
+
# All other variables become local, letting Ruby garbage collect them. This
|
63
|
+
# prevents us from having to manually reset them.
|
64
|
+
|
65
|
+
variable data @data;
|
66
|
+
variable p @current_pointer;
|
67
|
+
variable pe @data_ending_pointer;
|
68
|
+
variable cs @current_state;
|
69
|
+
variable ts @token_start;
|
70
|
+
variable te @token_end;
|
71
|
+
variable stack @stack;
|
72
|
+
variable act @ragel_act;
|
73
|
+
variable eof @eof;
|
74
|
+
variable stack @ragel_stack;
|
75
|
+
variable top @ragel_stack_top;
|
76
|
+
|
77
|
+
write data;
|
78
|
+
write init;
|
79
|
+
}%%##
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
def <<(new_data)
|
84
|
+
extend_buffer_with new_data
|
85
|
+
resume!
|
86
|
+
end
|
87
|
+
|
88
|
+
def resume!
|
89
|
+
%%{ write exec; }%%##
|
90
|
+
end
|
91
|
+
|
92
|
+
def extend_buffer_with(new_data)
|
93
|
+
if new_data.size + @data.size > BUFFER_SIZE
|
94
|
+
@data.slice! 0...new_data.size
|
95
|
+
# TODO: What if the current_pointer wasn't at the end of the data for some reason?
|
96
|
+
@current_pointer = @data.size
|
97
|
+
end
|
98
|
+
@data << new_data
|
99
|
+
@data_ending_pointer = @data.size
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
##
|
105
|
+
# Called after a response or event has been successfully parsed.
|
106
|
+
#
|
107
|
+
# @param [ManagerInterfaceResponse, ManagerInterfaceEvent] message The message just received
|
108
|
+
#
|
109
|
+
def message_received(message)
|
110
|
+
raise NotImplementedError, "Must be implemented in subclass!"
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Called when there is an Error: stanza on the socket. Could be caused by executing an unrecognized command, trying
|
115
|
+
# to originate into an invalid priority, etc. Note: many errors' responses are actually tightly coupled to a
|
116
|
+
# ManagerInterfaceEvent which comes directly after it. Often the message will say something like "Channel status
|
117
|
+
# will follow".
|
118
|
+
#
|
119
|
+
# @param [String] reason The reason given in the Message: header for the error stanza.
|
120
|
+
#
|
121
|
+
def error_received(reason)
|
122
|
+
raise NotImplementedError, "Must be implemented in subclass!"
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Called when there's a syntax error on the socket. This doesn't happen as often as it should because, in many cases,
|
127
|
+
# it's impossible to distinguish between a syntax error and an immediate packet.
|
128
|
+
#
|
129
|
+
# @param [String] ignored_chunk The offending text which caused the syntax error.
|
130
|
+
def syntax_error_encountered(ignored_chunk)
|
131
|
+
raise NotImplementedError, "Must be implemented in subclass!"
|
132
|
+
end
|
133
|
+
|
134
|
+
def init_success
|
135
|
+
@current_message = ManagerInterfaceResponse.new
|
136
|
+
end
|
137
|
+
|
138
|
+
def init_response_follows
|
139
|
+
@current_message = ManagerInterfaceResponse.new
|
140
|
+
end
|
141
|
+
|
142
|
+
def init_error
|
143
|
+
@current_message = ManagerInterfaceError.new()
|
144
|
+
end
|
145
|
+
|
146
|
+
def version_starts
|
147
|
+
@start_of_version = @current_pointer
|
148
|
+
end
|
149
|
+
|
150
|
+
def version_stops
|
151
|
+
self.ami_version = @data[@start_of_version...@current_pointer].to_f
|
152
|
+
@start_of_version = nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def event_name_starts
|
156
|
+
@event_name_start = @current_pointer
|
157
|
+
end
|
158
|
+
|
159
|
+
def event_name_stops
|
160
|
+
event_name = @data[@event_name_start...@current_pointer]
|
161
|
+
@event_name_start = nil
|
162
|
+
@current_message = ManagerInterfaceEvent.new(event_name)
|
163
|
+
end
|
164
|
+
|
165
|
+
def key_starts
|
166
|
+
@current_key_position = @current_pointer
|
167
|
+
end
|
168
|
+
|
169
|
+
def key_stops
|
170
|
+
@current_key = @data[@current_key_position...@current_pointer]
|
171
|
+
end
|
172
|
+
|
173
|
+
def value_starts
|
174
|
+
@current_value_position = @current_pointer
|
175
|
+
end
|
176
|
+
|
177
|
+
def value_stops
|
178
|
+
@current_value = @data[@current_value_position...@current_pointer]
|
179
|
+
@last_seen_value_end = @current_pointer + 2 # 2 for \r\n
|
180
|
+
add_pair_to_current_message
|
181
|
+
end
|
182
|
+
|
183
|
+
def error_reason_starts
|
184
|
+
@error_reason_start = @current_pointer
|
185
|
+
end
|
186
|
+
|
187
|
+
def error_reason_stops
|
188
|
+
@current_message.message = @data[@error_reason_start...@current_pointer]
|
189
|
+
end
|
190
|
+
|
191
|
+
def follows_text_starts
|
192
|
+
@follows_text_start = @current_pointer
|
193
|
+
end
|
194
|
+
|
195
|
+
def follows_text_stops
|
196
|
+
text = @data[@last_seen_value_end..(@current_pointer - "\r\n--END COMMAND--".size)]
|
197
|
+
@current_message.text_body = text
|
198
|
+
@follows_text_start = nil
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_pair_to_current_message
|
202
|
+
@current_message[@current_key] = @current_value
|
203
|
+
reset_key_and_value_positions
|
204
|
+
end
|
205
|
+
|
206
|
+
def reset_key_and_value_positions
|
207
|
+
@current_key, @current_value, @current_key_position, @current_value_position = nil
|
208
|
+
end
|
209
|
+
|
210
|
+
def syntax_error_starts
|
211
|
+
@current_syntax_error_start = @current_pointer # Adding 1 since the pointer is still set to the last successful match
|
212
|
+
end
|
213
|
+
|
214
|
+
def syntax_error_stops
|
215
|
+
# Subtracting 3 from @current_pointer below for "\r\n" which separates a stanza
|
216
|
+
offending_data = @data[@current_syntax_error_start...@current_pointer - 1]
|
217
|
+
syntax_error_encountered offending_data
|
218
|
+
@current_syntax_error_start = nil
|
219
|
+
end
|
220
|
+
|
221
|
+
def immediate_response_starts
|
222
|
+
@immediate_response_start = @current_pointer
|
223
|
+
end
|
224
|
+
|
225
|
+
def immediate_response_stops
|
226
|
+
message = @data[@immediate_response_start...(@current_pointer -1)]
|
227
|
+
message_received ManagerInterfaceResponse.from_immediate_response(message)
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# This method is used primarily in debugging.
|
232
|
+
#
|
233
|
+
def view_buffer(message=nil)
|
234
|
+
|
235
|
+
message ||= "Viewing the buffer"
|
236
|
+
|
237
|
+
buffer = @data.clone
|
238
|
+
buffer.insert(@current_pointer, "\033[0;31m\033[1;31m^\033[0m")
|
239
|
+
|
240
|
+
buffer.gsub!("\r", "\\\\r")
|
241
|
+
buffer.gsub!("\n", "\\n\n")
|
242
|
+
|
243
|
+
puts <<-INSPECTION
|
244
|
+
VVVVVVVVVVVVVVVVVVVVVVVVVVVVV
|
245
|
+
#### #{message}
|
246
|
+
#############################
|
247
|
+
#{buffer}
|
248
|
+
#############################
|
249
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
250
|
+
INSPECTION
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|
254
|
+
class DelegatingAsteriskManagerInterfaceLexer < AbstractAsteriskManagerInterfaceStreamLexer
|
255
|
+
|
256
|
+
def initialize(delegate, method_delegation_map=nil)
|
257
|
+
super()
|
258
|
+
@delegate = delegate
|
259
|
+
|
260
|
+
@message_received_method = method_delegation_map && method_delegation_map.has_key?(:message_received) ?
|
261
|
+
method_delegation_map[:message_received] : :message_received
|
262
|
+
|
263
|
+
@error_received_method = method_delegation_map && method_delegation_map.has_key?(:error_received) ?
|
264
|
+
method_delegation_map[:error_received] : :error_received
|
265
|
+
|
266
|
+
@syntax_error_method = method_delegation_map && method_delegation_map.has_key?(:syntax_error_encountered) ?
|
267
|
+
method_delegation_map[:syntax_error_encountered] : :syntax_error_encountered
|
268
|
+
end
|
269
|
+
|
270
|
+
def message_received(message)
|
271
|
+
@delegate.send(@message_received_method, message)
|
272
|
+
end
|
273
|
+
|
274
|
+
def error_received(message)
|
275
|
+
@delegate.send(@error_received_method, message)
|
276
|
+
end
|
277
|
+
|
278
|
+
def syntax_error_encountered(ignored_chunk)
|
279
|
+
@delegate.send(@syntax_error_method, ignored_chunk)
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
module VoIP
|
3
|
+
module Asterisk
|
4
|
+
module Manager
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
# This is the object containing a response from Asterisk.
|
9
|
+
#
|
10
|
+
# Note: not all responses have an ActionID!
|
11
|
+
#
|
12
|
+
class ManagerInterfaceResponse
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def from_immediate_response(text)
|
16
|
+
returning new do |instance|
|
17
|
+
instance.text_body = text
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :action,
|
23
|
+
:action_id,
|
24
|
+
:text_body # For "Response: Follows" sections
|
25
|
+
attr_reader :events
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@headers = HashWithIndifferentAccess.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_text_body?
|
32
|
+
!! @text_body
|
33
|
+
end
|
34
|
+
|
35
|
+
def headers
|
36
|
+
@headers.clone
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](arg)
|
40
|
+
@headers[arg]
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(key,value)
|
44
|
+
@headers[key] = value
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
class ManagerInterfaceError < Exception
|
50
|
+
|
51
|
+
attr_accessor :message
|
52
|
+
def initialize
|
53
|
+
@headers = HashWithIndifferentAccess.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def [](key)
|
57
|
+
@headers[key]
|
58
|
+
end
|
59
|
+
|
60
|
+
def []=(key,value)
|
61
|
+
@headers[key] = value
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
class ManagerInterfaceEvent < ManagerInterfaceResponse
|
67
|
+
|
68
|
+
attr_reader :name
|
69
|
+
def initialize(name)
|
70
|
+
super()
|
71
|
+
@name = name
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,87 @@
|
|
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 = crlf "--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; fgoto protocol; };
|
85
|
+
*|;
|
86
|
+
|
87
|
+
}%%
|