eric-adhearsion 0.7.999 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/CHANGELOG +8 -2
  2. data/EVENTS +11 -0
  3. data/Rakefile +96 -24
  4. data/adhearsion.gemspec +148 -0
  5. data/app_generators/ahn/ahn_generator.rb +24 -9
  6. data/app_generators/ahn/templates/.ahnrc +25 -3
  7. data/app_generators/ahn/templates/Rakefile +22 -2
  8. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  9. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  10. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  11. data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
  12. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  13. data/app_generators/ahn/templates/components/simon_game/{lib/simon_game.rb → simon_game.rb} +14 -19
  14. data/app_generators/ahn/templates/config/startup.rb +3 -6
  15. data/app_generators/ahn/templates/dialplan.rb +2 -3
  16. data/app_generators/ahn/templates/events.rb +32 -0
  17. data/bin/jahn +10 -0
  18. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  19. data/lib/adhearsion.rb +17 -11
  20. data/lib/adhearsion/cli.rb +141 -24
  21. data/lib/adhearsion/component_manager.rb +169 -238
  22. data/lib/adhearsion/component_manager/component_tester.rb +55 -0
  23. data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
  24. data/lib/adhearsion/events_support.rb +84 -0
  25. data/lib/adhearsion/{core_extensions → foundation}/all.rb +0 -0
  26. data/lib/adhearsion/{blank_slate.rb → foundation/blank_slate.rb} +0 -0
  27. data/lib/adhearsion/{core_extensions → foundation}/custom_daemonizer.rb +0 -0
  28. data/lib/adhearsion/foundation/event_socket.rb +203 -0
  29. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  30. data/lib/adhearsion/{core_extensions → foundation}/global.rb +0 -0
  31. data/lib/adhearsion/{core_extensions → foundation}/metaprogramming.rb +0 -0
  32. data/lib/adhearsion/foundation/numeric.rb +13 -0
  33. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  34. data/lib/adhearsion/{core_extensions → foundation}/relationship_properties.rb +2 -0
  35. data/lib/adhearsion/foundation/string.rb +26 -0
  36. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  37. data/lib/adhearsion/{core_extensions → foundation}/thread_safety.rb +0 -0
  38. data/lib/adhearsion/host_definitions.rb +5 -1
  39. data/lib/adhearsion/initializer.rb +229 -73
  40. data/lib/adhearsion/initializer/asterisk.rb +33 -11
  41. data/lib/adhearsion/initializer/configuration.rb +58 -6
  42. data/lib/adhearsion/initializer/database.rb +3 -46
  43. data/lib/adhearsion/initializer/drb.rb +9 -3
  44. data/lib/adhearsion/initializer/freeswitch.rb +3 -3
  45. data/lib/adhearsion/initializer/rails.rb +1 -1
  46. data/lib/adhearsion/tasks.rb +2 -1
  47. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  48. data/lib/adhearsion/version.rb +3 -3
  49. data/lib/adhearsion/voip/asterisk.rb +2 -2
  50. data/lib/adhearsion/voip/asterisk/agi_server.rb +9 -6
  51. data/lib/adhearsion/voip/asterisk/commands.rb +106 -4
  52. data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
  53. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
  54. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
  55. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  56. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  57. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  58. data/lib/adhearsion/voip/call.rb +51 -2
  59. data/lib/adhearsion/voip/dial_plan.rb +74 -61
  60. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +1 -1
  61. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +2 -6
  62. data/lib/adhearsion/voip/dsl/numerical_string.rb +2 -2
  63. data/lib/adhearsion/voip/freeswitch/oes_server.rb +2 -2
  64. data/lib/theatre.rb +151 -0
  65. data/lib/theatre/README.markdown +64 -0
  66. data/lib/theatre/callback_definition_loader.rb +84 -0
  67. data/lib/theatre/guid.rb +23 -0
  68. data/lib/theatre/invocation.rb +121 -0
  69. data/lib/theatre/namespace_manager.rb +153 -0
  70. data/lib/theatre/version.rb +2 -0
  71. metadata +63 -138
  72. data/Manifest.txt +0 -149
  73. data/README.txt +0 -6
  74. data/ahn_generators/component/USAGE +0 -5
  75. data/ahn_generators/component/component_generator.rb +0 -57
  76. data/ahn_generators/component/templates/configuration.rb +0 -0
  77. data/ahn_generators/component/templates/lib/lib.rb.erb +0 -3
  78. data/ahn_generators/component/templates/test/test.rb.erb +0 -12
  79. data/ahn_generators/component/templates/test/test_helper.rb +0 -14
  80. data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
  81. data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +0 -14
  82. data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +0 -31
  83. data/lib/adhearsion/core_extensions/array.rb +0 -0
  84. data/lib/adhearsion/core_extensions/guid.rb +0 -5
  85. data/lib/adhearsion/core_extensions/hash.rb +0 -0
  86. data/lib/adhearsion/core_extensions/numeric.rb +0 -4
  87. data/lib/adhearsion/core_extensions/proc.rb +0 -0
  88. data/lib/adhearsion/core_extensions/pseudo_uuid.rb +0 -11
  89. data/lib/adhearsion/core_extensions/publishable.rb +0 -73
  90. data/lib/adhearsion/core_extensions/string.rb +0 -26
  91. data/lib/adhearsion/core_extensions/thread.rb +0 -13
  92. data/lib/adhearsion/core_extensions/time.rb +0 -0
  93. data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
  94. data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
  95. data/lib/adhearsion/distributed/gateways/rest_gateway.rb +0 -9
  96. data/lib/adhearsion/distributed/gateways/soap_gateway.rb +0 -9
  97. data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +0 -9
  98. data/lib/adhearsion/distributed/peer_finder.rb +0 -0
  99. data/lib/adhearsion/distributed/remote_cli.rb +0 -0
  100. data/lib/adhearsion/hooks.rb +0 -57
  101. data/lib/adhearsion/initializer/paths.rb +0 -55
  102. data/lib/adhearsion/services/scheduler.rb +0 -5
  103. data/lib/adhearsion/voip/asterisk/ami.rb +0 -147
  104. data/lib/adhearsion/voip/asterisk/ami/actions.rb +0 -238
  105. data/lib/adhearsion/voip/asterisk/ami/machine.rb +0 -871
  106. data/lib/adhearsion/voip/asterisk/ami/machine.rl +0 -109
  107. data/lib/adhearsion/voip/asterisk/ami/parser.rb +0 -262
  108. data/script/destroy +0 -14
  109. data/script/generate +0 -14
  110. data/spec/fixtures/dialplan.rb +0 -3
  111. data/spec/initializer/test_configuration.rb +0 -267
  112. data/spec/initializer/test_loading.rb +0 -162
  113. data/spec/initializer/test_paths.rb +0 -43
  114. data/spec/silence.rb +0 -10
  115. data/spec/test_ahn_command.rb +0 -149
  116. data/spec/test_code_quality.rb +0 -87
  117. data/spec/test_component_manager.rb +0 -97
  118. data/spec/test_constants.rb +0 -8
  119. data/spec/test_drb.rb +0 -104
  120. data/spec/test_helper.rb +0 -94
  121. data/spec/test_hooks.rb +0 -37
  122. data/spec/test_host_definitions.rb +0 -79
  123. data/spec/test_initialization.rb +0 -105
  124. data/spec/test_logging.rb +0 -80
  125. data/spec/test_relationship_properties.rb +0 -54
  126. data/spec/voip/asterisk/ami_response_definitions.rb +0 -23
  127. data/spec/voip/asterisk/config_file_generators/test_agents.rb +0 -253
  128. data/spec/voip/asterisk/config_file_generators/test_queues.rb +0 -325
  129. data/spec/voip/asterisk/config_file_generators/test_voicemail.rb +0 -306
  130. data/spec/voip/asterisk/menu_command/test_calculated_match.rb +0 -111
  131. data/spec/voip/asterisk/menu_command/test_matchers.rb +0 -98
  132. data/spec/voip/asterisk/mock_ami_server.rb +0 -176
  133. data/spec/voip/asterisk/test_agi_server.rb +0 -451
  134. data/spec/voip/asterisk/test_ami.rb +0 -227
  135. data/spec/voip/asterisk/test_commands.rb +0 -2006
  136. data/spec/voip/asterisk/test_config_manager.rb +0 -129
  137. data/spec/voip/dsl/dispatcher_spec_helper.rb +0 -45
  138. data/spec/voip/dsl/test_dialing_dsl.rb +0 -268
  139. data/spec/voip/dsl/test_dispatcher.rb +0 -82
  140. data/spec/voip/dsl/test_parser.rb +0 -87
  141. data/spec/voip/freeswitch/test_basic_connection_manager.rb +0 -39
  142. data/spec/voip/freeswitch/test_inbound_connection_manager.rb +0 -39
  143. data/spec/voip/freeswitch/test_oes_server.rb +0 -9
  144. data/spec/voip/test_call_routing.rb +0 -127
  145. data/spec/voip/test_dialplan_manager.rb +0 -372
  146. data/spec/voip/test_numerical_string.rb +0 -48
  147. data/spec/voip/test_phone_number.rb +0 -36
  148. data/test/test_ahn_generator.rb +0 -59
  149. data/test/test_component_generator.rb +0 -52
  150. data/test/test_generator_helper.rb +0 -20
@@ -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
+ }%%