rene-adhearsion 0.8.6

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