rsmp 0.35.2 → 0.38.0

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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/devcontainer.json +22 -0
  3. data/.github/copilot-instructions.md +33 -0
  4. data/.github/workflows/copilot-setup-steps.yml +35 -0
  5. data/.github/workflows/rspec.yaml +2 -1
  6. data/.github/workflows/rubocop.yaml +17 -0
  7. data/.gitignore +7 -7
  8. data/.rubocop.yml +80 -0
  9. data/Gemfile +13 -1
  10. data/Gemfile.lock +73 -35
  11. data/Rakefile +3 -3
  12. data/bin/console +2 -4
  13. data/documentation/tasks.md +4 -4
  14. data/lib/rsmp/cli.rb +147 -124
  15. data/lib/rsmp/collect/ack_collector.rb +8 -7
  16. data/lib/rsmp/collect/aggregated_status_collector.rb +4 -4
  17. data/lib/rsmp/collect/alarm_collector.rb +31 -23
  18. data/lib/rsmp/collect/alarm_matcher.rb +3 -3
  19. data/lib/rsmp/collect/collector/logging.rb +17 -0
  20. data/lib/rsmp/collect/collector/reporting.rb +44 -0
  21. data/lib/rsmp/collect/collector/status.rb +34 -0
  22. data/lib/rsmp/collect/collector.rb +69 -150
  23. data/lib/rsmp/collect/command_matcher.rb +19 -6
  24. data/lib/rsmp/collect/command_response_collector.rb +7 -7
  25. data/lib/rsmp/collect/distributor.rb +14 -11
  26. data/lib/rsmp/collect/filter.rb +31 -15
  27. data/lib/rsmp/collect/matcher.rb +7 -11
  28. data/lib/rsmp/collect/queue.rb +4 -4
  29. data/lib/rsmp/collect/receiver.rb +10 -12
  30. data/lib/rsmp/collect/state_collector.rb +116 -77
  31. data/lib/rsmp/collect/status_collector.rb +6 -6
  32. data/lib/rsmp/collect/status_matcher.rb +17 -7
  33. data/lib/rsmp/{alarm_state.rb → component/alarm_state.rb} +76 -37
  34. data/lib/rsmp/{component.rb → component/component.rb} +15 -15
  35. data/lib/rsmp/component/component_base.rb +89 -0
  36. data/lib/rsmp/component/component_proxy.rb +75 -0
  37. data/lib/rsmp/component/components.rb +63 -0
  38. data/lib/rsmp/convert/export/json_schema.rb +116 -110
  39. data/lib/rsmp/convert/import/yaml.rb +21 -18
  40. data/lib/rsmp/{rsmp.rb → helpers/clock.rb} +5 -6
  41. data/lib/rsmp/{deep_merge.rb → helpers/deep_merge.rb} +2 -1
  42. data/lib/rsmp/helpers/error.rb +71 -0
  43. data/lib/rsmp/{inspect.rb → helpers/inspect.rb} +6 -10
  44. data/lib/rsmp/log/archive.rb +98 -0
  45. data/lib/rsmp/log/colorization.rb +41 -0
  46. data/lib/rsmp/log/filtering.rb +54 -0
  47. data/lib/rsmp/log/logger.rb +206 -0
  48. data/lib/rsmp/{logging.rb → log/logging.rb} +5 -7
  49. data/lib/rsmp/message.rb +159 -148
  50. data/lib/rsmp/{node.rb → node/node.rb} +19 -17
  51. data/lib/rsmp/node/protocol.rb +37 -0
  52. data/lib/rsmp/node/site/site.rb +195 -0
  53. data/lib/rsmp/node/supervisor/modules/configuration.rb +59 -0
  54. data/lib/rsmp/node/supervisor/modules/connection.rb +140 -0
  55. data/lib/rsmp/node/supervisor/modules/sites.rb +64 -0
  56. data/lib/rsmp/node/supervisor/supervisor.rb +72 -0
  57. data/lib/rsmp/{task.rb → node/task.rb} +29 -19
  58. data/lib/rsmp/proxy/modules/acknowledgements.rb +144 -0
  59. data/lib/rsmp/proxy/modules/receive.rb +119 -0
  60. data/lib/rsmp/proxy/modules/send.rb +76 -0
  61. data/lib/rsmp/proxy/modules/state.rb +25 -0
  62. data/lib/rsmp/proxy/modules/tasks.rb +105 -0
  63. data/lib/rsmp/proxy/modules/versions.rb +69 -0
  64. data/lib/rsmp/proxy/modules/watchdogs.rb +66 -0
  65. data/lib/rsmp/proxy/proxy.rb +199 -0
  66. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +52 -0
  67. data/lib/rsmp/proxy/site/modules/alarms.rb +27 -0
  68. data/lib/rsmp/proxy/site/modules/commands.rb +31 -0
  69. data/lib/rsmp/proxy/site/modules/status.rb +110 -0
  70. data/lib/rsmp/proxy/site/site_proxy.rb +205 -0
  71. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +47 -0
  72. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +73 -0
  73. data/lib/rsmp/proxy/supervisor/modules/commands.rb +53 -0
  74. data/lib/rsmp/proxy/supervisor/modules/status.rb +204 -0
  75. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +178 -0
  76. data/lib/rsmp/tlc/detector_logic.rb +18 -34
  77. data/lib/rsmp/tlc/input_states.rb +126 -0
  78. data/lib/rsmp/tlc/modules/detector_logics.rb +50 -0
  79. data/lib/rsmp/tlc/modules/display.rb +78 -0
  80. data/lib/rsmp/tlc/modules/helpers.rb +41 -0
  81. data/lib/rsmp/tlc/modules/inputs.rb +173 -0
  82. data/lib/rsmp/tlc/modules/modes.rb +253 -0
  83. data/lib/rsmp/tlc/modules/outputs.rb +30 -0
  84. data/lib/rsmp/tlc/modules/plans.rb +218 -0
  85. data/lib/rsmp/tlc/modules/signal_groups.rb +109 -0
  86. data/lib/rsmp/tlc/modules/startup_sequence.rb +22 -0
  87. data/lib/rsmp/tlc/modules/system.rb +140 -0
  88. data/lib/rsmp/tlc/modules/traffic_data.rb +49 -0
  89. data/lib/rsmp/tlc/signal_group.rb +37 -41
  90. data/lib/rsmp/tlc/signal_plan.rb +14 -11
  91. data/lib/rsmp/tlc/signal_priority.rb +39 -35
  92. data/lib/rsmp/tlc/startup_sequence.rb +59 -0
  93. data/lib/rsmp/tlc/traffic_controller.rb +39 -1006
  94. data/lib/rsmp/tlc/traffic_controller_site.rb +58 -57
  95. data/lib/rsmp/version.rb +1 -1
  96. data/lib/rsmp.rb +86 -49
  97. data/rsmp.gemspec +24 -30
  98. metadata +87 -130
  99. data/lib/rsmp/archive.rb +0 -76
  100. data/lib/rsmp/collect/message_matchers.rb +0 -0
  101. data/lib/rsmp/component_base.rb +0 -87
  102. data/lib/rsmp/component_proxy.rb +0 -57
  103. data/lib/rsmp/components.rb +0 -65
  104. data/lib/rsmp/error.rb +0 -71
  105. data/lib/rsmp/logger.rb +0 -216
  106. data/lib/rsmp/proxy.rb +0 -695
  107. data/lib/rsmp/site.rb +0 -188
  108. data/lib/rsmp/site_proxy.rb +0 -389
  109. data/lib/rsmp/supervisor.rb +0 -287
  110. data/lib/rsmp/supervisor_proxy.rb +0 -516
  111. data/lib/rsmp/tlc/inputs.rb +0 -134
data/lib/rsmp/proxy.rb DELETED
@@ -1,695 +0,0 @@
1
- # A connection to a remote site or supervisor.
2
- # Uses the Task module to handle asyncronous work, but adds
3
- # the concept of a connection that can be connected or disconnected.
4
-
5
- require 'rubygems'
6
-
7
- module RSMP
8
- class Proxy
9
- WRAPPING_DELIMITER = "\f"
10
-
11
- include Logging
12
- include Distributor
13
- include Inspect
14
- include Task
15
-
16
- attr_reader :state, :archive, :connection_info, :sxl, :collector, :ip, :port, :node, :core_version
17
- def initialize options
18
- @node = options[:node]
19
- initialize_logging options
20
- initialize_distributor
21
- initialize_task
22
- setup options
23
- clear
24
- @state = :disconnected
25
- @state_condition = Async::Notification.new
26
- end
27
-
28
-
29
- def now
30
- node.now
31
- end
32
-
33
- def disconnect
34
- end
35
-
36
-
37
- # wait for the reader task to complete,
38
- # which is not expected to happen before the connection is closed
39
- def wait_for_reader
40
- @reader.wait if @reader
41
- end
42
-
43
- # close connection, but keep our main task running so we can reconnect
44
- def close
45
- log "Closing connection", level: :warning
46
- close_stream
47
- close_socket
48
- stop_reader
49
- set_state :disconnected
50
- distribute_error DisconnectError.new("Connection was closed")
51
-
52
- # stop timer
53
- # as we're running inside the timer, code after stop_timer() will not be called,
54
- # unless it's in the ensure block
55
- stop_timer
56
- end
57
-
58
- def stop_subtasks
59
- stop_timer
60
- stop_reader
61
- clear
62
- super
63
- end
64
-
65
- def stop_timer
66
- @timer.stop if @timer
67
- ensure
68
- @timer = nil
69
- end
70
-
71
- def stop_reader
72
- @reader.stop if @reader
73
- ensure
74
- @reader = nil
75
- end
76
-
77
- def close_stream
78
- @stream.close if @stream
79
- ensure
80
- @stream = nil
81
- end
82
-
83
- def close_socket
84
- @socket.close if @socket
85
- ensure
86
- @socket = nil
87
- end
88
-
89
- def stop_task
90
- close
91
- super
92
- end
93
-
94
- # change our state
95
- def set_state state
96
- return if state == @state
97
- @state = state
98
- state_changed
99
- end
100
-
101
- # the state changed
102
- # override to to things like notifications
103
- def state_changed
104
- @state_condition.signal @state
105
- end
106
-
107
- # revive after a reconnect
108
- def revive options
109
- setup options
110
- end
111
-
112
- def setup options
113
- @settings = options[:settings]
114
- @socket = options[:socket]
115
- @stream = options[:stream]
116
- @protocol = options[:protocol]
117
- @ip = options[:ip]
118
- @port = options[:port]
119
- @connection_info = options[:info]
120
- @sxl = nil
121
- @site_settings = nil # can't pick until we know the site id
122
- if options[:collect]
123
- @collector = RSMP::Collector.new self, options[:collect]
124
- @collector.start
125
- end
126
- end
127
-
128
- def inspect
129
- "#<#{self.class.name}:#{self.object_id}, #{inspector(
130
- :@acknowledgements,:@settings,:@site_settings
131
- )}>"
132
- end
133
-
134
- def clock
135
- @node.clock
136
- end
137
-
138
- def ready?
139
- @state == :ready
140
- end
141
-
142
- def connected?
143
- @state == :connected || @state == :ready
144
- end
145
-
146
- def disconnected?
147
- @state == :disconnected
148
- end
149
-
150
- def clear
151
- @awaiting_acknowledgement = {}
152
- @latest_watchdog_received = nil
153
- @watchdog_started = false
154
- @version_determined = false
155
- @ingoing_acknowledged = {}
156
- @outgoing_acknowledged = {}
157
- @latest_watchdog_send_at = nil
158
-
159
- @acknowledgements = {}
160
- @acknowledgement_condition = Async::Notification.new
161
- end
162
-
163
- # run an async task that reads from @socket
164
- def start_reader
165
- @reader = @task.async do |task|
166
- task.annotate "reader"
167
- run_reader
168
- end
169
- end
170
-
171
- def run_reader
172
- @stream ||= Async::IO::Stream.new(@socket)
173
- @protocol ||= Async::IO::Protocol::Line.new(@stream,WRAPPING_DELIMITER) # rsmp messages are json terminated with a form-feed
174
- loop do
175
- read_line
176
- end
177
- rescue Restart
178
- log "Closing connection", level: :warning
179
- raise
180
- rescue Async::Wrapper::Cancelled
181
- # ignore exceptions raised when a wait is aborted because a task is stopped
182
- rescue EOFError, Async::Stop
183
- log "Connection closed", level: :warning
184
- rescue IOError => e
185
- log "IOError: #{e}", level: :warning
186
- rescue Errno::ECONNRESET
187
- log "Connection reset by peer", level: :warning
188
- rescue Errno::EPIPE
189
- log "Broken pipe", level: :warning
190
- rescue StandardError => e
191
- distribute_error e, level: :internal
192
- end
193
-
194
- def read_line
195
- json = @protocol.read_line
196
- beginning = Time.now
197
- message = process_packet json
198
- duration = Time.now - beginning
199
- ms = (duration*1000).round(4)
200
- if duration > 0
201
- per_second = (1.0 / duration).round
202
- else
203
- per_second = Float::INFINITY
204
- end
205
- if message
206
- type = message.type
207
- m_id = Logger.shorten_message_id(message.m_id)
208
- else
209
- type = 'Unknown'
210
- m_id = nil
211
- end
212
- str = [type,m_id,"processed in #{ms}ms, #{per_second}req/s"].compact.join(' ')
213
- log str, level: :statistics
214
- end
215
-
216
- def receive_error e, options={}
217
- @node.receive_error e, options
218
- end
219
-
220
- def start_watchdog
221
- log "Starting watchdog with interval #{@site_settings['intervals']['watchdog']} seconds", level: :debug
222
- @watchdog_started = true
223
- end
224
-
225
- def stop_watchdog
226
- log "Stopping watchdog", level: :debug
227
- @watchdog_started = false
228
- end
229
-
230
- def with_watchdog_disabled
231
- was = @watchdog_started
232
- stop_watchdog if was
233
- yield
234
- ensure
235
- start_watchdog if was
236
- end
237
-
238
-
239
- def start_timer
240
- return if @timer
241
- name = "timer"
242
- interval = @site_settings['intervals']['timer'] || 1
243
- log "Starting #{name} with interval #{interval} seconds", level: :debug
244
- @latest_watchdog_received = Clock.now
245
- @timer = @task.async do |task|
246
- task.annotate "timer"
247
- run_timer task, interval
248
- end
249
- end
250
-
251
- def run_timer task, interval
252
- next_time = Time.now.to_f
253
- loop do
254
- begin
255
- now = Clock.now
256
- timer(now)
257
- rescue RSMP::Schema::Error => e
258
- log "Timer: Schema error: #{e}", level: :warning
259
- rescue EOFError => e
260
- log "Timer: Connection closed: #{e}", level: :warning
261
- rescue IOError => e
262
- log "Timer: IOError", level: :warning
263
- rescue Errno::ECONNRESET
264
- log "Timer: Connection reset by peer", level: :warning
265
- rescue Errno::EPIPE => e
266
- log "Timer: Broken pipe", level: :warning
267
- rescue StandardError => e
268
- distribute_error e, level: :internal
269
- end
270
- ensure
271
- next_time += interval
272
- duration = next_time - Time.now.to_f
273
- task.sleep duration
274
- end
275
- end
276
-
277
- def timer now
278
- watchdog_send_timer now
279
- check_ack_timeout now
280
- check_watchdog_timeout now
281
- end
282
-
283
- def watchdog_send_timer now
284
- return unless @watchdog_started
285
- return if @site_settings['intervals']['watchdog'] == :never
286
- if @latest_watchdog_send_at == nil
287
- send_watchdog now
288
- else
289
- # we add half the timer interval to pick the timer
290
- # event closes to the wanted wathcdog interval
291
- diff = now - @latest_watchdog_send_at
292
- if (diff + 0.5*@site_settings['intervals']['timer']) >= (@site_settings['intervals']['watchdog'])
293
- send_watchdog now
294
- end
295
- end
296
- end
297
-
298
- def send_watchdog now=Clock.now
299
- message = Watchdog.new( {"wTs" => clock.to_s})
300
- send_message message
301
- @latest_watchdog_send_at = now
302
- end
303
-
304
- def check_ack_timeout now
305
- timeout = @site_settings['timeouts']['acknowledgement']
306
- # hash cannot be modify during iteration, so clone it
307
- @awaiting_acknowledgement.clone.each_pair do |m_id, message|
308
- latest = message.timestamp + timeout
309
- if now > latest
310
- str = "No acknowledgements for #{message.type} #{message.m_id_short} within #{timeout} seconds"
311
- log str, level: :error
312
- begin
313
- close
314
- ensure
315
- distribute_error MissingAcknowledgment.new(str)
316
- end
317
- end
318
- end
319
- end
320
-
321
- def check_watchdog_timeout now
322
- timeout = @site_settings['timeouts']['watchdog']
323
- latest = @latest_watchdog_received + timeout
324
- left = latest - now
325
- if left < 0
326
- str = "No Watchdog received within #{timeout} seconds"
327
- log str, level: :warning
328
- distribute MissingWatchdog.new(str)
329
- end
330
- end
331
-
332
- def log str, options={}
333
- super str, options.merge(ip: @ip, port: @port, site_id: @site_id)
334
- end
335
-
336
- def get_schemas
337
- schemas = { core: RSMP::Schema.latest_core_version } # use latest core
338
- schemas[:core] = core_version if core_version
339
- schemas[sxl] = RSMP::Schema.sanitize_version(sxl_version.to_s) if sxl && sxl_version
340
- schemas
341
- end
342
-
343
- def send_message message, reason=nil, validate: true, force: false
344
- raise NotReady unless connected? unless force
345
- raise IOError unless @protocol
346
- message.direction = :out
347
- message.generate_json
348
- message.validate get_schemas unless validate==false
349
- @protocol.write_lines message.json
350
- expect_acknowledgement message
351
- distribute message
352
- log_send message, reason
353
- rescue EOFError, IOError
354
- buffer_message message
355
- rescue SchemaError, RSMP::Schema::Error => e
356
- schemas_string = e.schemas.map {|schema| "#{schema.first}: #{schema.last}"}.join(", ")
357
- str = "Could not send #{message.type} because schema validation failed (#{schemas_string}): #{e.message}"
358
- log str, message: message, level: :error
359
- distribute_error e.exception("#{str} #{message.json}")
360
- end
361
-
362
- def buffer_message message
363
- # TODO
364
- #log "Cannot send #{message.type} because the connection is closed.", message: message, level: :error
365
- end
366
-
367
- def log_send message, reason=nil
368
- if reason
369
- str = "Sent #{message.type} #{reason}"
370
- else
371
- str = "Sent #{message.type}"
372
- end
373
-
374
- if message.type == "MessageNotAck"
375
- log str, message: message, level: :warning
376
- else
377
- log str, message: message, level: :log
378
- end
379
- end
380
-
381
- def should_validate_ingoing_message? message
382
- return true unless @site_settings
383
- skip = @site_settings.dig('skip_validation')
384
- return true unless skip
385
- klass = message.class.name.split('::').last
386
- !skip.include?(klass)
387
- end
388
-
389
- def process_deferred
390
- @node.process_deferred
391
- end
392
-
393
- def verify_sequence message
394
- expect_version_message(message) unless @version_determined
395
- end
396
-
397
- def process_packet json
398
- attributes = Message.parse_attributes json
399
- message = Message.build attributes, json
400
- message.validate(get_schemas) if should_validate_ingoing_message?(message)
401
- verify_sequence message
402
- with_deferred_distribution do
403
- distribute message
404
- process_message message
405
- end
406
- process_deferred
407
- message
408
- rescue InvalidPacket => e
409
- str = "Received invalid package, must be valid JSON but got #{json.size} bytes: #{e.message}"
410
- distribute_error e.exception(str)
411
- log str, level: :warning
412
- nil
413
- rescue MalformedMessage => e
414
- str = "Received malformed message, #{e.message}"
415
- distribute_error e.exception(str)
416
- log str, message: Malformed.new(attributes), level: :warning
417
- # cannot send NotAcknowledged for a malformed message since we can't read it, just ignore it
418
- nil
419
- rescue SchemaError, RSMP::Schema::Error => e
420
- schemas_string = e.schemas.map {|schema| "#{schema.first}: #{schema.last}"}.join(", ")
421
- reason = "schema errors (#{schemas_string}): #{e.message}"
422
- str = "Received invalid #{message.type}"
423
- distribute_error e.exception(str), message: message
424
- dont_acknowledge message, str, reason
425
- message
426
- rescue InvalidMessage => e
427
- reason = "#{e.message}"
428
- str = "Received invalid #{message.type},"
429
- distribute_error e.exception("#{str} #{message.json}"), message: message
430
- dont_acknowledge message, str, reason
431
- message
432
- rescue FatalError => e
433
- reason = e.message
434
- str = "Rejected #{message.type},"
435
- distribute_error e.exception(str), message: message
436
- dont_acknowledge message, str, reason
437
- close
438
- message
439
- ensure
440
- @node.clear_deferred
441
- end
442
-
443
- def process_message message
444
- case message
445
- when MessageAck
446
- process_ack message
447
- when MessageNotAck
448
- process_not_ack message
449
- when Version
450
- process_version message
451
- when Watchdog
452
- process_watchdog message
453
- else
454
- dont_acknowledge message, "Received", "unknown message (#{message.type})"
455
- end
456
- end
457
-
458
- def will_not_handle message
459
- reason = "since we're a #{self.class.name.downcase}" unless reason
460
- log "Ignoring #{message.type}, #{reason}", message: message, level: :warning
461
- dont_acknowledge message, nil, reason
462
- end
463
-
464
- def expect_acknowledgement message
465
- unless message.is_a?(MessageAck) || message.is_a?(MessageNotAck)
466
- @awaiting_acknowledgement[message.m_id] = message
467
- end
468
- end
469
-
470
- def dont_expect_acknowledgement message
471
- @awaiting_acknowledgement.delete message.attribute("oMId")
472
- end
473
-
474
- def extraneous_version message
475
- dont_acknowledge message, "Received", "extraneous Version message"
476
- end
477
-
478
- def core_versions
479
- version = @site_settings["core_version"]
480
- if version == 'latest'
481
- [RSMP::Schema.latest_core_version]
482
- elsif version
483
- [version]
484
- else
485
- RSMP::Schema.core_versions
486
- end
487
- end
488
-
489
- def check_core_version message
490
- versions = core_versions
491
- # find versions that both we and the client support
492
- candidates = message.versions & versions
493
- if candidates.any?
494
- @core_version = candidates.sort_by { |v| Gem::Version.new(v) }.last # pick latest version
495
- else
496
- reason = "RSMP versions [#{message.versions.join(', ')}] requested, but only [#{versions.join(', ')}] supported."
497
- dont_acknowledge message, "Version message rejected", reason, force: true
498
- raise HandshakeError.new reason
499
- end
500
- end
501
-
502
- def process_version message
503
- end
504
-
505
- def acknowledge original
506
- raise InvalidArgument unless original
507
- ack = MessageAck.build_from(original)
508
- ack.original = original.clone
509
- send_message ack, "for #{ack.original.type} #{original.m_id_short}"
510
- check_ingoing_acknowledged original
511
- end
512
-
513
- def dont_acknowledge original, prefix=nil, reason=nil, force: true
514
- raise InvalidArgument unless original
515
- str = [prefix,reason].join(' ')
516
- log str, message: original, level: :warning if reason
517
- message = MessageNotAck.new({
518
- "oMId" => original.m_id,
519
- "rea" => reason || "Unknown reason"
520
- })
521
- message.original = original.clone
522
- send_message message, "for #{original.type} #{original.m_id_short}", force: force
523
- end
524
-
525
- def wait_for_state state, timeout:
526
- states = [state].flatten
527
- return if states.include?(@state)
528
- wait_for_condition(@state_condition,timeout: timeout) do
529
- states.include?(@state)
530
- end
531
- @state
532
- rescue RSMP::TimeoutError
533
- raise RSMP::TimeoutError.new "Did not reach state #{state} within #{timeout}s"
534
- end
535
-
536
- def send_version site_id, core_versions
537
- if core_versions=='latest'
538
- versions = [RSMP::Schema.latest_core_version]
539
- elsif core_versions=='all'
540
- versions = RSMP::Schema.core_versions
541
- else
542
- versions = [core_versions].flatten
543
- end
544
- versions_array = versions.map {|v| {"vers" => v} }
545
-
546
- site_id_array = [site_id].flatten.map {|id| {"sId" => id} }
547
-
548
- version_response = Version.new({
549
- "RSMP"=>versions_array,
550
- "siteId"=>site_id_array,
551
- "SXL"=>sxl_version.to_s
552
- })
553
- send_message version_response
554
- end
555
-
556
- def find_original_for_message message
557
- @awaiting_acknowledgement[ message.attribute("oMId") ]
558
- end
559
-
560
- # TODO this might be better handled by a proper event machine using e.g. the EventMachine gem
561
- def check_outgoing_acknowledged message
562
- unless @outgoing_acknowledged[message.type]
563
- @outgoing_acknowledged[message.type] = true
564
- acknowledged_first_outgoing message
565
- end
566
- end
567
-
568
- def check_ingoing_acknowledged message
569
- unless @ingoing_acknowledged[message.type]
570
- @ingoing_acknowledged[message.type] = true
571
- acknowledged_first_ingoing message
572
- end
573
- end
574
-
575
- def acknowledged_first_outgoing message
576
- end
577
-
578
- def acknowledged_first_ingoing message
579
- end
580
-
581
- def process_ack message
582
- original = find_original_for_message message
583
- if original
584
- dont_expect_acknowledgement message
585
- message.original = original
586
- log_acknowledgement_for_original message, original
587
-
588
- case original.type
589
- when "Version"
590
- version_acknowledged
591
- when "StatusSubscribe"
592
- status_subscribe_acknowledged original
593
- end
594
-
595
- check_outgoing_acknowledged original
596
-
597
- @acknowledgements[ original.m_id ] = message
598
- @acknowledgement_condition.signal message
599
- else
600
- log_acknowledgement_for_unknown message
601
- end
602
- end
603
-
604
- def process_not_ack message
605
- original = find_original_for_message message
606
- if original
607
- dont_expect_acknowledgement message
608
- message.original = original
609
- log_acknowledgement_for_original message, original
610
- @acknowledgements[ original.m_id ] = message
611
- @acknowledgement_condition.signal message
612
- else
613
- log_acknowledgement_for_unknown message
614
- end
615
- end
616
-
617
- def log_acknowledgement_for_original message, original
618
- str = "Received #{message.type} for #{original.type} #{message.attribute("oMId")[0..3]}"
619
- if message.type == 'MessageNotAck'
620
- reason = message.attributes["rea"]
621
- str = "#{str}: #{reason}" if reason
622
- log str, message: message, level: :warning
623
- else
624
- log str, message: message, level: :log
625
- end
626
- end
627
-
628
- def log_acknowledgement_for_unknown message
629
- log "Received #{message.type} for unknown message #{message.attribute("oMId")[0..3]}", message: message, level: :warning
630
- end
631
-
632
- def process_watchdog message
633
- log "Received #{message.type}", message: message, level: :log
634
- @latest_watchdog_received = Clock.now
635
- acknowledge message
636
- end
637
-
638
- def expect_version_message message
639
- unless message.is_a?(Version) || message.is_a?(MessageAck) || message.is_a?(MessageNotAck)
640
- raise HandshakeError.new "Version must be received first"
641
- end
642
- end
643
-
644
- def handshake_complete
645
- set_state :ready
646
- end
647
-
648
- def version_acknowledged
649
- end
650
-
651
- def author
652
- @node.site_id
653
- end
654
-
655
- def send_and_optionally_collect message, options, &block
656
- collect_options = options[:collect] || options[:collect!]
657
- if collect_options
658
- task = @task.async do |task|
659
- task.annotate 'send_and_optionally_collect'
660
- collector = yield collect_options # call block to create collector
661
- collector.collect
662
- collector.ok! if options[:collect!] # raise any errors if the bang version was specified
663
- collector
664
- end
665
-
666
- send_message message, validate: options[:validate]
667
- { sent: message, collector: task.wait }
668
- else
669
- send_message message, validate: options[:validate]
670
- return { sent: message }
671
- end
672
- end
673
-
674
- def set_nts_message_attributes message
675
- message.attributes['ntsOId'] = (main && main.ntsOId) ? main.ntsOId : ''
676
- message.attributes['xNId'] = (main && main.xNId) ? main.xNId : ''
677
- end
678
-
679
- # Use Gem class to check version requirement
680
- # Requirement must be a string like '1.1', '>=1.0.3' or '<2.1.4',
681
- # or list of strings, like ['<=1.4','<1.5']
682
- def self.version_meets_requirement? version, requirement
683
- Gem::Requirement.new(requirement).satisfied_by?(Gem::Version.new(version))
684
- end
685
-
686
- def status_subscribe_acknowledged original
687
- component = find_component original.attribute('cId')
688
- return unless component
689
- short = Message.shorten_m_id original.m_id
690
- subscribe_list = original.attributes['sS']
691
- log "StatusSubscribe #{short} acknowledged, allowing repeated status values for #{subscribe_list}", level: :info
692
- component.allow_repeat_updates subscribe_list
693
- end
694
- end
695
- end