adhearsion-cw 1.0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/CHANGELOG +109 -0
  2. data/EVENTS +11 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +456 -0
  5. data/Rakefile +134 -0
  6. data/adhearsion.gemspec +174 -0
  7. data/app_generators/ahn/USAGE +5 -0
  8. data/app_generators/ahn/ahn_generator.rb +97 -0
  9. data/app_generators/ahn/templates/.ahnrc +34 -0
  10. data/app_generators/ahn/templates/Gemfile +7 -0
  11. data/app_generators/ahn/templates/README +8 -0
  12. data/app_generators/ahn/templates/Rakefile +27 -0
  13. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  14. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  15. data/app_generators/ahn/templates/components/disabled/restful_rpc/README.markdown +11 -0
  16. data/app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb +48 -0
  17. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +91 -0
  18. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.yml +34 -0
  19. data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +251 -0
  20. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +104 -0
  21. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.yml +2 -0
  22. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  23. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  24. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.yml +12 -0
  25. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/README.markdown +3 -0
  26. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/xmpp_gateway.rb +11 -0
  27. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/xmpp_gateway.yml +0 -0
  28. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
  29. data/app_generators/ahn/templates/config/startup.rb +74 -0
  30. data/app_generators/ahn/templates/dialplan.rb +3 -0
  31. data/app_generators/ahn/templates/events.rb +32 -0
  32. data/bin/ahn +29 -0
  33. data/bin/ahnctl +68 -0
  34. data/bin/jahn +43 -0
  35. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  36. data/lib/adhearsion/cli.rb +296 -0
  37. data/lib/adhearsion/component_manager/component_tester.rb +53 -0
  38. data/lib/adhearsion/component_manager/spec_framework.rb +18 -0
  39. data/lib/adhearsion/component_manager.rb +272 -0
  40. data/lib/adhearsion/events_support.rb +84 -0
  41. data/lib/adhearsion/foundation/all.rb +15 -0
  42. data/lib/adhearsion/foundation/blank_slate.rb +3 -0
  43. data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
  44. data/lib/adhearsion/foundation/event_socket.rb +205 -0
  45. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  46. data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
  47. data/lib/adhearsion/foundation/numeric.rb +13 -0
  48. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  49. data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
  50. data/lib/adhearsion/foundation/string.rb +26 -0
  51. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  52. data/lib/adhearsion/foundation/thread_safety.rb +7 -0
  53. data/lib/adhearsion/host_definitions.rb +67 -0
  54. data/lib/adhearsion/initializer/asterisk.rb +87 -0
  55. data/lib/adhearsion/initializer/configuration.rb +321 -0
  56. data/lib/adhearsion/initializer/database.rb +60 -0
  57. data/lib/adhearsion/initializer/drb.rb +31 -0
  58. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  59. data/lib/adhearsion/initializer/ldap.rb +57 -0
  60. data/lib/adhearsion/initializer/rails.rb +41 -0
  61. data/lib/adhearsion/initializer/xmpp.rb +42 -0
  62. data/lib/adhearsion/initializer.rb +394 -0
  63. data/lib/adhearsion/logging.rb +92 -0
  64. data/lib/adhearsion/tasks/components.rb +32 -0
  65. data/lib/adhearsion/tasks/database.rb +5 -0
  66. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  67. data/lib/adhearsion/tasks/generating.rb +20 -0
  68. data/lib/adhearsion/tasks/lint.rb +4 -0
  69. data/lib/adhearsion/tasks/testing.rb +37 -0
  70. data/lib/adhearsion/tasks.rb +17 -0
  71. data/lib/adhearsion/version.rb +35 -0
  72. data/lib/adhearsion/voip/asterisk/agi_server.rb +115 -0
  73. data/lib/adhearsion/voip/asterisk/commands.rb +1581 -0
  74. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  75. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +102 -0
  76. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  77. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  78. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  79. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1681 -0
  80. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +341 -0
  81. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  82. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  83. data/lib/adhearsion/voip/asterisk/manager_interface.rb +705 -0
  84. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  85. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  86. data/lib/adhearsion/voip/asterisk.rb +4 -0
  87. data/lib/adhearsion/voip/call.rb +498 -0
  88. data/lib/adhearsion/voip/call_routing.rb +64 -0
  89. data/lib/adhearsion/voip/commands.rb +9 -0
  90. data/lib/adhearsion/voip/constants.rb +39 -0
  91. data/lib/adhearsion/voip/conveniences.rb +18 -0
  92. data/lib/adhearsion/voip/dial_plan.rb +250 -0
  93. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  94. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  95. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  96. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  97. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +69 -0
  98. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  99. data/lib/adhearsion/voip/dsl/numerical_string.rb +128 -0
  100. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  101. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  102. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  103. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  104. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  105. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  106. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  107. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +57 -0
  108. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  109. data/lib/adhearsion/xmpp/connection.rb +61 -0
  110. data/lib/adhearsion.rb +46 -0
  111. data/lib/theatre/README.markdown +64 -0
  112. data/lib/theatre/callback_definition_loader.rb +84 -0
  113. data/lib/theatre/guid.rb +23 -0
  114. data/lib/theatre/invocation.rb +121 -0
  115. data/lib/theatre/namespace_manager.rb +153 -0
  116. data/lib/theatre/version.rb +2 -0
  117. data/lib/theatre.rb +151 -0
  118. metadata +323 -0
@@ -0,0 +1,341 @@
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 = 128.kilobytes unless defined? BUFFER_SIZE
10
+
11
+ ##
12
+ # IMPORTANT! See method documentation for adjust_pointers!
13
+ #
14
+ # @see adjust_pointers
15
+ #
16
+ POINTERS = [
17
+ :@current_pointer,
18
+ :@token_start,
19
+ :@token_end,
20
+ :@version_start,
21
+ :@event_name_start,
22
+ :@current_key_position,
23
+ :@current_value_position,
24
+ :@last_seen_value_end,
25
+ :@error_reason_start,
26
+ :@follows_text_start,
27
+ :@current_syntax_error_start,
28
+ :@immediate_response_start
29
+ ]
30
+
31
+ %%{
32
+ machine ami_protocol_parser;
33
+
34
+ # All required Ragel actions are implemented as Ruby methods.
35
+
36
+ # Executed after a "Response: Success" or "Response: Pong"
37
+ action init_success { init_success }
38
+
39
+ action init_response_follows { init_response_follows }
40
+
41
+ action init_error { init_error }
42
+
43
+ action message_received { message_received @current_message }
44
+ action error_received { error_received @current_message }
45
+
46
+ action version_starts { version_starts }
47
+ action version_stops { version_stops }
48
+
49
+ action key_starts { key_starts }
50
+ action key_stops { key_stops }
51
+
52
+ action value_starts { value_starts }
53
+ action value_stops { value_stops }
54
+
55
+ action error_reason_starts { error_reason_starts }
56
+ action error_reason_stops { error_reason_stops }
57
+
58
+ action syntax_error_starts { syntax_error_starts }
59
+ action syntax_error_stops { syntax_error_stops }
60
+
61
+ action immediate_response_starts { immediate_response_starts }
62
+ action immediate_response_stops { immediate_response_stops }
63
+
64
+ action follows_text_starts { follows_text_starts }
65
+ action follows_text_stops { follows_text_stops }
66
+
67
+ action event_name_starts { event_name_starts }
68
+ action event_name_stops { event_name_stops }
69
+
70
+ include ami_protocol_parser_machine "ami_protocol_lexer_machine.rl";
71
+
72
+ }%%##
73
+
74
+ attr_accessor(:ami_version)
75
+ def initialize
76
+
77
+ @data = ""
78
+ @current_pointer = 0
79
+ @ragel_stack = []
80
+ @ami_version = 0.0
81
+
82
+ %%{
83
+ # All other variables become local, letting Ruby garbage collect them. This
84
+ # prevents us from having to manually reset them.
85
+
86
+ variable data @data;
87
+ variable p @current_pointer;
88
+ variable pe @data_ending_pointer;
89
+ variable cs @current_state;
90
+ variable ts @token_start;
91
+ variable te @token_end;
92
+ variable act @ragel_act;
93
+ variable eof @eof;
94
+ variable stack @ragel_stack;
95
+ variable top @ragel_stack_top;
96
+
97
+ write data;
98
+ write init;
99
+ }%%##
100
+
101
+ end
102
+
103
+ def <<(new_data)
104
+ extend_buffer_with new_data
105
+ resume!
106
+ end
107
+
108
+ def resume!
109
+ %%{ write exec; }%%##
110
+ end
111
+
112
+ def extend_buffer_with(new_data)
113
+ length = new_data.size
114
+
115
+ if length > BUFFER_SIZE
116
+ raise Exception, "ERROR: Buffer overrun! Input size (#{new_data.size}) larger than buffer (#{BUFFER_SIZE})"
117
+ end
118
+
119
+ if length + @data.size > BUFFER_SIZE
120
+ if @data.size != @current_pointer
121
+ if @current_pointer < length
122
+ # We are about to shift more bytes off the array than we have
123
+ # parsed. This will cause the parser to lose state so
124
+ # integrity cannot be guaranteed.
125
+ 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}"
126
+ end
127
+ end
128
+ @data.slice! 0...length
129
+ adjust_pointers -length
130
+ end
131
+ @data << new_data
132
+ @data_ending_pointer = @data.size
133
+ end
134
+
135
+ protected
136
+
137
+ ##
138
+ # This method will adjust all pointers into the buffer according
139
+ # to the supplied offset. This is necessary any time the buffer
140
+ # changes, for example when the sliding window is incremented forward
141
+ # after new data is received.
142
+ #
143
+ # It is VERY IMPORTANT that when any additional pointers are defined
144
+ # that they are added to this method. Unpredictable results may
145
+ # otherwise occur!
146
+ #
147
+ # @see https://adhearsion.lighthouseapp.com/projects/5871-adhearsion/tickets/72-ami-lexer-buffer-offset#ticket-72-26
148
+ #
149
+ # @param offset Adjust pointers by offset. May be negative.
150
+ #
151
+ def adjust_pointers(offset)
152
+ POINTERS.each do |ptr|
153
+ value = instance_variable_get(ptr)
154
+ instance_variable_set(ptr, value + offset) if !value.nil?
155
+ end
156
+ end
157
+
158
+ ##
159
+ # Called after a response or event has been successfully parsed.
160
+ #
161
+ # @param [ManagerInterfaceResponse, ManagerInterfaceEvent] message The message just received
162
+ #
163
+ def message_received(message)
164
+ raise NotImplementedError, "Must be implemented in subclass!"
165
+ end
166
+
167
+ ##
168
+ # Called when there is an Error: stanza on the socket. Could be caused by executing an unrecognized command, trying
169
+ # to originate into an invalid priority, etc. Note: many errors' responses are actually tightly coupled to a
170
+ # ManagerInterfaceEvent which comes directly after it. Often the message will say something like "Channel status
171
+ # will follow".
172
+ #
173
+ # @param [String] reason The reason given in the Message: header for the error stanza.
174
+ #
175
+ def error_received(reason)
176
+ raise NotImplementedError, "Must be implemented in subclass!"
177
+ end
178
+
179
+ ##
180
+ # Called when there's a syntax error on the socket. This doesn't happen as often as it should because, in many cases,
181
+ # it's impossible to distinguish between a syntax error and an immediate packet.
182
+ #
183
+ # @param [String] ignored_chunk The offending text which caused the syntax error.
184
+ def syntax_error_encountered(ignored_chunk)
185
+ raise NotImplementedError, "Must be implemented in subclass!"
186
+ end
187
+
188
+ def init_success
189
+ @current_message = ManagerInterfaceResponse.new
190
+ end
191
+
192
+ def init_response_follows
193
+ @current_message = ManagerInterfaceResponse.new
194
+ end
195
+
196
+ def init_error
197
+ @current_message = ManagerInterfaceError.new()
198
+ end
199
+
200
+ def version_starts
201
+ @version_start = @current_pointer
202
+ end
203
+
204
+ def version_stops
205
+ self.ami_version = @data[@version_start...@current_pointer].to_f
206
+ @version_start = nil
207
+ end
208
+
209
+ def event_name_starts
210
+ @event_name_start = @current_pointer
211
+ end
212
+
213
+ def event_name_stops
214
+ event_name = @data[@event_name_start...@current_pointer]
215
+ @event_name_start = nil
216
+ @current_message = ManagerInterfaceEvent.new(event_name)
217
+ end
218
+
219
+ def key_starts
220
+ @current_key_position = @current_pointer
221
+ end
222
+
223
+ def key_stops
224
+ @current_key = @data[@current_key_position...@current_pointer]
225
+ end
226
+
227
+ def value_starts
228
+ @current_value_position = @current_pointer
229
+ end
230
+
231
+ def value_stops
232
+ @current_value = @data[@current_value_position...@current_pointer]
233
+ @last_seen_value_end = @current_pointer + 2 # 2 for \r\n
234
+ add_pair_to_current_message
235
+ end
236
+
237
+ def error_reason_starts
238
+ @error_reason_start = @current_pointer
239
+ end
240
+
241
+ def error_reason_stops
242
+ @current_message.message = @data[@error_reason_start...@current_pointer]
243
+ end
244
+
245
+ def follows_text_starts
246
+ @follows_text_start = @current_pointer
247
+ end
248
+
249
+ def follows_text_stops
250
+ text = @data[@last_seen_value_end..@current_pointer]
251
+ text.sub! /\r?\n--END COMMAND--/, ""
252
+ @current_message.text_body = text
253
+ @follows_text_start = nil
254
+ end
255
+
256
+ def add_pair_to_current_message
257
+ @current_message[@current_key] = @current_value
258
+ reset_key_and_value_positions
259
+ end
260
+
261
+ def reset_key_and_value_positions
262
+ @current_key, @current_value, @current_key_position, @current_value_position = nil
263
+ end
264
+
265
+ def syntax_error_starts
266
+ @current_syntax_error_start = @current_pointer # Adding 1 since the pointer is still set to the last successful match
267
+ end
268
+
269
+ def syntax_error_stops
270
+ # Subtracting 3 from @current_pointer below for "\r\n" which separates a stanza
271
+ offending_data = @data[@current_syntax_error_start...@current_pointer - 1]
272
+ syntax_error_encountered offending_data
273
+ @current_syntax_error_start = nil
274
+ end
275
+
276
+ def immediate_response_starts
277
+ @immediate_response_start = @current_pointer
278
+ end
279
+
280
+ def immediate_response_stops
281
+ message = @data[@immediate_response_start...(@current_pointer -1)]
282
+ message_received ManagerInterfaceResponse.from_immediate_response(message)
283
+ end
284
+
285
+ ##
286
+ # This method is used primarily in debugging.
287
+ #
288
+ def view_buffer(message=nil)
289
+
290
+ message ||= "Viewing the buffer"
291
+
292
+ buffer = @data.clone
293
+ buffer.insert(@current_pointer, "\033[0;31m\033[1;31m^\033[0m")
294
+
295
+ buffer.gsub!("\r", "\\\\r")
296
+ buffer.gsub!("\n", "\\n\n")
297
+
298
+ puts <<-INSPECTION
299
+ VVVVVVVVVVVVVVVVVVVVVVVVVVVVV
300
+ #### #{message}
301
+ #############################
302
+ #{buffer}
303
+ #############################
304
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
305
+ INSPECTION
306
+
307
+ end
308
+ end
309
+ class DelegatingAsteriskManagerInterfaceLexer < AbstractAsteriskManagerInterfaceStreamLexer
310
+
311
+ def initialize(delegate, method_delegation_map=nil)
312
+ super()
313
+ @delegate = delegate
314
+
315
+ @message_received_method = method_delegation_map && method_delegation_map.has_key?(:message_received) ?
316
+ method_delegation_map[:message_received] : :message_received
317
+
318
+ @error_received_method = method_delegation_map && method_delegation_map.has_key?(:error_received) ?
319
+ method_delegation_map[:error_received] : :error_received
320
+
321
+ @syntax_error_method = method_delegation_map && method_delegation_map.has_key?(:syntax_error_encountered) ?
322
+ method_delegation_map[:syntax_error_encountered] : :syntax_error_encountered
323
+ end
324
+
325
+ def message_received(message)
326
+ @delegate.send(@message_received_method, message)
327
+ end
328
+
329
+ def error_received(message)
330
+ @delegate.send(@error_received_method, message)
331
+ end
332
+
333
+ def syntax_error_encountered(ignored_chunk)
334
+ @delegate.send(@syntax_error_method, ignored_chunk)
335
+ end
336
+
337
+ end
338
+ end
339
+ end
340
+ end
341
+ 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
+ new.tap 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 < StandardError
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 = 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
+ }%%