rsmp 0.51.4 → 0.53.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile.lock +1 -1
  4. data/documentation/collecting_message.md +112 -0
  5. data/documentation/configuration.md +53 -2
  6. data/lib/rsmp/collect/collector.rb +6 -0
  7. data/lib/rsmp/collect/queue.rb +4 -0
  8. data/lib/rsmp/collect/status_collector.rb +2 -2
  9. data/lib/rsmp/component/component_base.rb +10 -1
  10. data/lib/rsmp/component/component_proxy.rb +1 -1
  11. data/lib/rsmp/component/components.rb +24 -2
  12. data/lib/rsmp/node/site/site.rb +21 -7
  13. data/lib/rsmp/node/site/status.rb +39 -0
  14. data/lib/rsmp/node/supervisor/modules/connection.rb +20 -4
  15. data/lib/rsmp/node/supervisor/modules/sites.rb +2 -0
  16. data/lib/rsmp/options/schemas/required_settings.json +20 -12
  17. data/lib/rsmp/options/schemas/supervisor_site.json +1 -1
  18. data/lib/rsmp/options/site_options.rb +1 -0
  19. data/lib/rsmp/proxy/modules/acknowledgements.rb +2 -0
  20. data/lib/rsmp/proxy/modules/receive.rb +32 -2
  21. data/lib/rsmp/proxy/modules/send.rb +23 -3
  22. data/lib/rsmp/proxy/modules/version_validation.rb +76 -0
  23. data/lib/rsmp/proxy/modules/versions.rb +21 -22
  24. data/lib/rsmp/proxy/proxy.rb +29 -5
  25. data/lib/rsmp/proxy/site/modules/aggregated_status.rb +22 -2
  26. data/lib/rsmp/proxy/site/modules/commands.rb +1 -1
  27. data/lib/rsmp/proxy/site/site_proxy.rb +22 -13
  28. data/lib/rsmp/proxy/site/sxl_selection.rb +13 -22
  29. data/lib/rsmp/proxy/supervisor/modules/aggregated_status.rb +56 -1
  30. data/lib/rsmp/proxy/supervisor/modules/alarms.rb +11 -4
  31. data/lib/rsmp/proxy/supervisor/modules/commands.rb +4 -3
  32. data/lib/rsmp/proxy/supervisor/modules/message_buffer.rb +6 -1
  33. data/lib/rsmp/proxy/supervisor/modules/status.rb +46 -1
  34. data/lib/rsmp/proxy/supervisor/modules/status_updates.rb +17 -1
  35. data/lib/rsmp/proxy/supervisor/supervisor_proxy.rb +12 -16
  36. data/lib/rsmp/schema/validation.rb +2 -1
  37. data/lib/rsmp/version.rb +1 -1
  38. data/schemas/core/3.1.2/definitions.json +5 -0
  39. data/schemas/core/3.1.2/status_subscribe.json +1 -1
  40. data/schemas/core/3.1.3/definitions.json +5 -0
  41. data/schemas/core/3.1.3/status_subscribe.json +1 -1
  42. data/schemas/core/3.1.4/definitions.json +5 -0
  43. data/schemas/core/3.1.4/status_subscribe.json +1 -1
  44. data/schemas/core/3.1.5/definitions.json +5 -0
  45. data/schemas/core/3.1.5/status_subscribe.json +1 -1
  46. data/schemas/core/3.2.0/definitions.json +5 -0
  47. data/schemas/core/3.2.0/status_subscribe.json +1 -1
  48. data/schemas/core/3.2.1/definitions.json +5 -0
  49. data/schemas/core/3.2.1/status_subscribe.json +1 -1
  50. data/schemas/core/3.2.2/definitions.json +5 -0
  51. data/schemas/core/3.2.2/status_subscribe.json +1 -1
  52. data/schemas/core/3.3.0/aggregated_status.json +17 -11
  53. data/schemas/core/3.3.0/aggregated_status_request.json +4 -4
  54. data/schemas/core/3.3.0/component_list.json +1 -1
  55. data/schemas/core/3.3.0/definitions.json +23 -2
  56. data/schemas/core/3.3.0/status_subscribe.json +14 -4
  57. data/schemas/core/3.3.0/version.json +46 -26
  58. data/schemas/tlc/1.0.10/defs/definitions.json +5 -0
  59. data/schemas/tlc/1.0.13/defs/definitions.json +5 -0
  60. data/schemas/tlc/1.0.14/defs/definitions.json +5 -0
  61. data/schemas/tlc/1.0.15/defs/definitions.json +5 -0
  62. data/schemas/tlc/1.0.7/defs/definitions.json +5 -0
  63. data/schemas/tlc/1.0.8/defs/definitions.json +5 -0
  64. data/schemas/tlc/1.0.9/defs/definitions.json +5 -0
  65. data/schemas/tlc/1.1.0/defs/definitions.json +5 -0
  66. data/schemas/tlc/1.2.0/defs/definitions.json +5 -0
  67. data/schemas/tlc/1.2.1/defs/definitions.json +5 -0
  68. data/schemas/tlc/1.3.0/defs/definitions.json +7 -0
  69. metadata +3 -1
@@ -38,8 +38,24 @@ module RSMP
38
38
  write_protocol(message)
39
39
  end
40
40
 
41
+ def core_3_3_send_failure(message)
42
+ return unless core_3_3_0_or_newer?
43
+ return 'Alarm exchange is disabled' if message.is_a?(Alarm) && !use_alarms?
44
+
45
+ 'Communication establishment is incomplete' if !ready? && application_message?(message)
46
+ end
47
+
48
+ def nts_attributes?(message)
49
+ message.attributes.key?('cId') || message.is_a?(Alarm) ||
50
+ message.is_a?(AggregatedStatus) || message.is_a?(AggregatedStatusRequest)
51
+ end
52
+
41
53
  def prepare_message(message, validate:)
54
+ permission = core_3_3_send_failure(message)
55
+ return invalid_outbound_result(message, permission) if permission
56
+
42
57
  message.direction = :out
58
+ apply_nts_message_attributes(message) if core_3_3_0_or_newer? && nts_attributes?(message)
43
59
  message.encode_for(schemas) unless validate == false
44
60
  message.generate_json
45
61
  unless validate == false
@@ -135,10 +151,14 @@ module RSMP
135
151
  end
136
152
 
137
153
  def apply_nts_message_attributes(message)
138
- return if core_3_3?
154
+ if core_3_3_0_or_newer?
155
+ message.attributes['ntsOId'] = ''
156
+ message.attributes['xNId'] = ''
157
+ return
158
+ end
139
159
 
140
- message.attributes['ntsOId'] = main && main.ntsoid ? main.ntsoid : ''
141
- message.attributes['xNId'] = main && main.xnid ? main.xnid : ''
160
+ message.attributes['ntsOId'] = main&.ntsoid || ''
161
+ message.attributes['xNId'] = main&.xnid || ''
142
162
  end
143
163
  end
144
164
  end
@@ -0,0 +1,76 @@
1
+ module RSMP
2
+ class Proxy
3
+ module Modules
4
+ # Shape and cross-message constraints for version negotiation.
5
+ module VersionValidation
6
+ def validate_version_array!(message)
7
+ items = message.attributes['RSMP']
8
+ valid = items.is_a?(Array) && !items.empty? && items.all? do |item|
9
+ item.is_a?(Hash) && item['vers'].is_a?(String) &&
10
+ (RSMP::Schema.strict_version?(item['vers']) || RSMP::Schema.normalize_core_version(item['vers']))
11
+ end
12
+ raise HandshakeError, 'Invalid RSMP version array' unless valid
13
+ end
14
+
15
+ def validate_version!(message, step:)
16
+ validation = message.validate(core: core_version)
17
+ raise HandshakeError, validation.message if validation.invalid?
18
+ return unless core_3_3_0_or_newer?
19
+
20
+ raise HandshakeError, "Expected Version #{step}" unless message.step == step
21
+
22
+ validate_sxl_names!(message.sxls)
23
+ validate_primary_sxl!(message) if step == 'Request'
24
+ end
25
+
26
+ def validate_sxl_names!(items)
27
+ names = items.map { |item| item['name'] }
28
+ raise HandshakeError, 'Duplicate SXL names' unless names.uniq == names
29
+
30
+ items.each do |item|
31
+ version = item['version']
32
+ if version && !RSMP::Schema.strict_version?(version)
33
+ raise HandshakeError, 'SXL version must use MAJOR.MINOR.PATCH'
34
+ end
35
+ end
36
+ end
37
+
38
+ def validate_primary_sxl!(message)
39
+ primary = message.attributes['SXL']
40
+ return unless primary && !primary.empty? && message.sxls.none? { |item| item['version'] == primary }
41
+
42
+ raise HandshakeError, 'Primary SXL version does not match SXLS'
43
+ end
44
+
45
+ def validate_sxl_response!(sxls)
46
+ offered = sxl_request_items
47
+ names = offered.map { |item| item['name'] }
48
+ returned = sxls.reject { |item| item['status'] == 'expected' }.map { |item| item['name'] }
49
+ raise HandshakeError, 'SXLS must report every offered SXL exactly once' unless returned.sort == names.sort
50
+
51
+ sxls.each { |item| validate_sxl_status!(item, names) }
52
+ end
53
+
54
+ def validate_sxl_status!(item, names)
55
+ case item['status']
56
+ when 'ok'
57
+ raise HandshakeError, 'SXL version must match the request exactly' unless valid_sxl_response?(item)
58
+ when 'expected'
59
+ raise HandshakeError, 'Expected SXL was offered' if names.include?(item['name'])
60
+ when 'mismatch'
61
+ supported = item['supported']
62
+ unless supported == supported.sort_by { |version| Gem::Version.new(version) }
63
+ raise HandshakeError, 'Supported SXL versions must be sorted'
64
+ end
65
+ end
66
+ end
67
+
68
+ def valid_sxl_response?(accepted)
69
+ requested = sxl_request_items.find { |item| item['name'] == accepted['name'] }
70
+ RSMP::Schema.strict_version?(accepted['version']) && requested &&
71
+ requested['version'] == accepted['version']
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,9 +1,13 @@
1
+ require_relative 'version_validation'
2
+
1
3
  module RSMP
2
4
  class Proxy
3
5
  module Modules
4
6
  # Version negotiation and handling
5
7
  # Manages RSMP version handshake between sites and supervisors
6
8
  module Versions
9
+ include VersionValidation
10
+
7
11
  def core_versions
8
12
  version = @site_settings['core_version']
9
13
  if version == 'latest'
@@ -25,8 +29,10 @@ module RSMP
25
29
  versions.uniq
26
30
  end
27
31
 
28
- def core_3_3?
29
- core_version && version_meets_requirement?(core_version, '>=3.3.0')
32
+ def core_3_3_0_or_newer?
33
+ return false unless core_version
34
+
35
+ version_meets_requirement?(core_version, '>=3.3.0')
30
36
  end
31
37
 
32
38
  def configured_sxls
@@ -48,7 +54,8 @@ module RSMP
48
54
  end
49
55
 
50
56
  def check_core_version(message)
51
- versions = core_versions
57
+ validate_version_array!(message)
58
+ versions = @offered_core_versions || core_versions
52
59
  candidates = message.versions.filter_map do |wire_version|
53
60
  normalized = RSMP::Schema.normalize_core_version(wire_version)
54
61
  [wire_version, normalized] if normalized && versions.include?(normalized)
@@ -60,7 +67,6 @@ module RSMP
60
67
  else
61
68
  reason = "RSMP versions [#{message.versions.join(', ')}] requested, " \
62
69
  "but only [#{versions.join(', ')}] supported."
63
- dont_acknowledge message, 'Version message rejected', reason, force: true
64
70
  raise HandshakeError, reason
65
71
  end
66
72
  end
@@ -80,13 +86,13 @@ module RSMP
80
86
  end
81
87
 
82
88
  def send_version_response(site_id, core_versions)
83
- if core_3_3?
89
+ if core_3_3_0_or_newer?
84
90
  send_generated_message Version.new({
85
91
  'step' => 'Response',
86
92
  'RSMP' => [{ 'vers' => core_version }],
87
93
  'supervisorId' => site_id,
88
94
  'SXLS' => version_response_sxls,
89
- 'receiveAlarms' => @site_settings['receive_alarms'] != false
95
+ 'useAlarms' => @site_settings['use_alarms'] != false
90
96
  }), validate: false
91
97
  else
92
98
  send_legacy_version_response(site_id, core_versions)
@@ -106,7 +112,14 @@ module RSMP
106
112
 
107
113
  def send_version_message(site_id, core_versions, step:)
108
114
  attributes = version_message_attributes(site_id, core_versions)
109
- attributes.merge!(version_request_attributes) if step == 'Request'
115
+ if step == 'Request'
116
+ @offered_core_versions = normalized_core_versions(core_versions).filter_map do |version|
117
+ RSMP::Schema.normalize_core_version(version)
118
+ end
119
+ legacy_sxl = attributes['SXL']
120
+ attributes.merge!(version_request_attributes)
121
+ attributes['SXL'] = legacy_sxl unless @offered_core_versions.include?('3.3.0')
122
+ end
110
123
  send_generated_message Version.new(attributes), validate: false
111
124
  end
112
125
 
@@ -151,6 +164,7 @@ module RSMP
151
164
  def version_request_attributes
152
165
  {
153
166
  'step' => 'Request',
167
+ 'SXL' => primary_configured_sxl ? sxl_request_items.first['version'] : '',
154
168
  'SXLS' => sxl_request_items
155
169
  }
156
170
  end
@@ -159,21 +173,6 @@ module RSMP
159
173
  accepted_sxls + rejected_sxls
160
174
  end
161
175
 
162
- def validate_sxl_response!(sxls)
163
- invalid = sxls.find { |accepted| !valid_sxl_response?(accepted) }
164
- return unless invalid
165
-
166
- raise HandshakeError,
167
- "Invalid SXL version #{invalid['name']} #{invalid['version']}; " \
168
- 'Core 3.3 requires an exact MAJOR.MINOR.PATCH match'
169
- end
170
-
171
- def valid_sxl_response?(accepted)
172
- requested = sxl_request_items.find { |item| item['name'] == accepted['name'] }
173
- RSMP::Schema.strict_version?(accepted['version']) && requested &&
174
- requested['version'] == accepted['version']
175
- end
176
-
177
176
  def version_acknowledged; end
178
177
 
179
178
  def component_list_acknowledged; end
@@ -80,6 +80,10 @@ module RSMP
80
80
  @ingoing_acknowledged = {}
81
81
  @outgoing_acknowledged = {}
82
82
  @latest_watchdog_send_at = nil
83
+ @last_site_status = nil
84
+ @last_component_list = nil
85
+ @initial_aggregated_status_id = nil
86
+ @site_aggregated_status = nil
83
87
  @component_list_received = false
84
88
  @outgoing_watchdog_acknowledged = false
85
89
  @sxl_interfaces = {}
@@ -104,11 +108,17 @@ module RSMP
104
108
  @sxls = []
105
109
  @accepted_sxls = []
106
110
  @rejected_sxls = []
107
- @receive_alarms = true
111
+ @use_alarms = true
108
112
  @site_settings = nil # can't pick until we know the site id
109
- return unless options[:collect]
113
+ collect = options[:collect]
114
+ return unless collect
115
+
116
+ @collector = if collect.is_a?(Proc)
117
+ collect.call(self)
118
+ else
119
+ RSMP::Collector.new self, collect
120
+ end
110
121
 
111
- @collector = RSMP::Collector.new self, options[:collect]
112
122
  @collector.start
113
123
  end
114
124
 
@@ -129,8 +139,22 @@ module RSMP
129
139
  schemas
130
140
  end
131
141
 
132
- def receive_alarms?
133
- @receive_alarms != false
142
+ def application_message?(message)
143
+ !%w[Version Watchdog MessageAck MessageNotAck ComponentList AggregatedStatus].include?(message.type)
144
+ end
145
+
146
+ def sxl_message_allowed?(message)
147
+ return true unless core_3_3_0_or_newer?
148
+ return false if message.is_a?(Alarm) && !use_alarms?
149
+ return true if RSMP::Schema.core_message_type?(message.attributes)
150
+
151
+ !RSMP::Schema.resolve_sxl(message.attributes, schemas: schemas).nil?
152
+ rescue RSMP::Schema::UnknownMessageCodeError
153
+ false
154
+ end
155
+
156
+ def use_alarms?
157
+ !core_3_3_0_or_newer? || @use_alarms != false
134
158
  end
135
159
 
136
160
  def primary_sxl
@@ -4,7 +4,7 @@ module RSMP
4
4
  # Handles aggregated status requests and responses
5
5
  module AggregatedStatus
6
6
  # Build and send an AggregatedStatusRequest. Returns Result<AggregatedStatusRequest>.
7
- def request_aggregated_status(component, m_id: nil, validate: true)
7
+ def request_aggregated_status(component = nil, m_id: nil, validate: true)
8
8
  readiness = validate_ready 'request aggregated status'
9
9
  return readiness if readiness.failure?
10
10
 
@@ -13,6 +13,7 @@ module RSMP
13
13
  'cId' => component,
14
14
  'mId' => m_id
15
15
  })
16
+ message.attributes.delete('cId') if core_3_3_0_or_newer?
16
17
  apply_nts_message_attributes message
17
18
  send_message(message, validate: validate).map(&:message)
18
19
  end
@@ -22,7 +23,7 @@ module RSMP
22
23
  end
23
24
 
24
25
  # Build, send an AggregatedStatusRequest and collect the response. Returns Result<Exchange>.
25
- def request_aggregated_status_and_collect(component, within:, m_id: nil, validate: true)
26
+ def request_aggregated_status_and_collect(component = nil, within:, m_id: nil, validate: true)
26
27
  readiness = validate_ready 'request aggregated status'
27
28
  return readiness if readiness.failure?
28
29
 
@@ -31,6 +32,7 @@ module RSMP
31
32
  'cId' => component,
32
33
  'mId' => m_id
33
34
  })
35
+ message.attributes.delete('cId') if core_3_3_0_or_newer?
34
36
  apply_nts_message_attributes message
35
37
  collector = AggregatedStatusCollector.new(self, timeout: within, m_id: m_id, num: 1)
36
38
  send_message_and_collect(message, collector, validate: validate)
@@ -48,6 +50,8 @@ module RSMP
48
50
  end
49
51
 
50
52
  def process_aggregated_status(message)
53
+ return process_site_aggregated_status(message) if core_3_3_0_or_newer?
54
+
51
55
  status_elements = message.attribute('se')
52
56
  validate_aggregated_status(message, status_elements)
53
57
  c_id = message.attributes['cId']
@@ -64,6 +68,22 @@ module RSMP
64
68
  acknowledge message
65
69
  end
66
70
 
71
+ def process_site_aggregated_status(message)
72
+ @site_aggregated_status = message.attribute('se').dup.freeze
73
+ acknowledge message
74
+ @supervisor.site_aggregated_status_changed(self)
75
+ complete_initial_status(message)
76
+ end
77
+
78
+ attr_reader :site_aggregated_status
79
+
80
+ def complete_initial_status(message)
81
+ return unless core_3_3_0_or_newer? && !ready? && @component_list_received && @outgoing_watchdog_acknowledged
82
+ return unless message.is_a?(RSMP::AggregatedStatus)
83
+
84
+ handshake_complete
85
+ end
86
+
67
87
  def aggregated_status_changed(component, _options = {})
68
88
  @supervisor.aggregated_status_changed self, component
69
89
  end
@@ -66,7 +66,7 @@ module RSMP
66
66
  end
67
67
 
68
68
  def process_command_response(message)
69
- return reject_multiple_command_codes(message) if core_3_3? && multiple_command_codes?(message)
69
+ return reject_multiple_command_codes(message) if core_3_3_0_or_newer? && multiple_command_codes?(message)
70
70
 
71
71
  log "Received #{message.type}", message: message, level: :log
72
72
  acknowledge message
@@ -86,8 +86,9 @@ module RSMP
86
86
  log "Received Version message for site #{@site_id}", message: message, level: :log
87
87
  start_timer
88
88
  acknowledge message
89
- response_id = core_3_3? ? (@supervisor.site_id || @site_id) : @site_id
89
+ response_id = core_3_3_0_or_newer? ? (@supervisor.site_id || @site_id) : @site_id
90
90
  send_version_response response_id, core_versions
91
+ @use_alarms = @site_settings['use_alarms'] != false if core_3_3_0_or_newer?
91
92
  @version_determined = true
92
93
  end
93
94
 
@@ -101,9 +102,9 @@ module RSMP
101
102
  def acknowledged_first_outgoing(message)
102
103
  case message.type
103
104
  when 'Watchdog'
104
- if core_3_3?
105
+ if core_3_3_0_or_newer?
105
106
  @outgoing_watchdog_acknowledged = true
106
- handshake_complete if @component_list_received
107
+
107
108
  else
108
109
  handshake_complete
109
110
  end
@@ -130,30 +131,35 @@ module RSMP
130
131
  def process_component_list(message)
131
132
  log "Received #{message.type}", message: message, level: :log
132
133
  rebuild_components_from_list message.attributes['components']
133
- build_sxl_interfaces
134
134
  acknowledge message
135
135
  @component_list_received = true
136
- handshake_complete if @outgoing_watchdog_acknowledged
137
136
  end
138
137
 
139
138
  def rebuild_components_from_list(items)
140
139
  main_id = @site_settings.dig('components', 'main')&.keys&.first
140
+ ids = items.map { |item| item['id'] }
141
+ raise MessageRejected, 'Duplicate component IDs' unless ids.uniq == ids
142
+
143
+ previous = @components
141
144
  @components = {}
142
145
  @main = nil
143
146
  items.each do |item|
144
147
  grouped = item['id'] == main_id
145
- component = ComponentProxy.new(
146
- id: item['id'],
147
- node: self,
148
- type: item['type'],
149
- name: item['name'],
150
- grouped: grouped
151
- )
148
+ component = listed_component(item, previous[item['id']], grouped)
152
149
  @components[component.c_id] = component
153
150
  @main = component if grouped
154
151
  end
155
152
  end
156
153
 
154
+ def listed_component(item, previous, grouped)
155
+ if previous && previous.component_type == item['type']
156
+ previous.update_metadata(name: item['name'] || item['id'])
157
+ return previous
158
+ end
159
+
160
+ ComponentProxy.new(id: item['id'], node: self, type: item['type'], name: item['name'], grouped: grouped)
161
+ end
162
+
157
163
  def site_ids_changed
158
164
  @supervisor.site_ids_changed
159
165
  end
@@ -165,8 +171,9 @@ module RSMP
165
171
  def process_version(message)
166
172
  return extraneous_version message if @version_determined
167
173
 
168
- check_site_ids message
169
174
  check_core_version message
175
+ validate_version! message, step: 'Request'
176
+ check_site_ids message
170
177
  check_sxl_version message
171
178
  version_accepted message
172
179
  end
@@ -174,6 +181,8 @@ module RSMP
174
181
  def check_site_ids(message)
175
182
  # RSMP support multiple site ids. we don't support this yet. instead we use the first id only
176
183
  site_id = message.attribute('siteId').map { |item| item['sId'] }.first
184
+ raise HandshakeError, 'Unexpected site identity' if core_3_3_0_or_newer? && @site_id && @site_id != site_id
185
+
177
186
  @site_id ||= site_id
178
187
  @supervisor.check_site_id site_id
179
188
  site_ids_changed
@@ -2,7 +2,7 @@ module RSMP
2
2
  # Selects the SXL versions accepted by a supervisor-side site proxy.
3
3
  module SiteSxlSelection
4
4
  def check_sxl_version(message)
5
- if core_3_3?
5
+ if core_3_3_0_or_newer?
6
6
  select_sxls message
7
7
  else
8
8
  select_legacy_sxl message
@@ -26,34 +26,25 @@ module RSMP
26
26
  end
27
27
 
28
28
  def select_sxls(message)
29
- selected_sxls = message.sxls.map { |requested| select_sxl(requested) }
30
- @accepted_sxls, @rejected_sxls = selected_sxls.partition { |item| item['rejected'].nil? }
29
+ selected = message.sxls.map { |requested| select_sxl(requested) }
30
+ offered = message.sxls.map { |item| item['name'] }
31
+ configured_sxls.each do |item|
32
+ selected << { 'name' => item['name'], 'status' => 'expected' } unless offered.include?(item['name'])
33
+ end
34
+ @accepted_sxls, @rejected_sxls = selected.partition { |item| item['status'] == 'ok' }
31
35
  end
32
36
 
33
37
  def select_sxl(requested)
34
38
  configured = configured_sxls.find { |item| item['name'] == requested['name'] }
35
- return rejected_sxl(requested, 1, 'SXL not supported') unless configured
36
-
37
- unless RSMP::Schema.strict_version?(requested['version'])
38
- return rejected_sxl(requested, 2, 'Core 3.3 requires SXL versions to use MAJOR.MINOR.PATCH')
39
- end
39
+ return { 'name' => requested['name'], 'status' => 'unsupported' } unless configured
40
40
 
41
- configured_version = RSMP::Schema.sanitize_version(configured['version'].to_s)
42
- if configured_version == requested['version']
43
- RSMP::Schema.find_schema! requested['name'], requested['version']
44
- requested.slice('name', 'version', 'prefix')
41
+ version = RSMP::Schema.sanitize_version(configured['version'].to_s)
42
+ if version == requested['version']
43
+ RSMP::Schema.find_schema! requested['name'], version
44
+ { 'name' => requested['name'], 'status' => 'ok', 'version' => version }
45
45
  else
46
- rejected_sxl(requested, 2, "Supervisor only supports #{configured['version']}")
46
+ { 'name' => requested['name'], 'status' => 'mismatch', 'supported' => [version] }
47
47
  end
48
48
  end
49
-
50
- def rejected_sxl(requested, code, reason)
51
- {
52
- 'name' => requested['name'],
53
- 'version' => requested['version'],
54
- 'rejected' => code,
55
- 'reason' => reason
56
- }.compact
57
- end
58
49
  end
59
50
  end
@@ -3,14 +3,44 @@ module RSMP
3
3
  module Modules
4
4
  # Handles aggregated status messages
5
5
  module AggregatedStatus
6
+ def send_component_list
7
+ components = @site.component_list
8
+ result = send_generated_message ComponentList.new('components' => components)
9
+ @last_component_list = JSON.parse(JSON.generate(components)) if result.success?
10
+ result
11
+ end
12
+
13
+ def synchronize_site_state
14
+ send_component_list if @site.component_list != @last_component_list
15
+ send_changed_site_status
16
+ end
17
+
18
+ def component_list_acknowledged
19
+ return if ready? || @initial_aggregated_status_id
20
+
21
+ @initial_aggregated_status_id = Message.make_m_id
22
+ send_aggregated_status(nil, m_id: @initial_aggregated_status_id)
23
+ end
24
+
25
+ def initial_status_acknowledged(original)
26
+ return unless core_3_3_0_or_newer? && original.m_id == @initial_aggregated_status_id
27
+
28
+ @initial_aggregated_status_id = nil
29
+ handshake_complete unless ready?
30
+ end
31
+
6
32
  def send_all_aggregated_status
33
+ return send_site_aggregated_status if core_3_3_0_or_newer?
34
+
7
35
  @site.components.each_pair do |_c_id, component|
8
36
  send_aggregated_status component if component.grouped
9
37
  end
10
38
  end
11
39
 
12
40
  # Send aggregated status for a component
13
- def send_aggregated_status(component, m_id: nil)
41
+ def send_aggregated_status(component = nil, m_id: nil)
42
+ return send_site_aggregated_status(m_id: m_id) if core_3_3_0_or_newer?
43
+
14
44
  m_id ||= RSMP::Message.make_m_id
15
45
 
16
46
  se = if core_version && Proxy.version_meets_requirement?(core_version, '<=3.1.2')
@@ -32,8 +62,33 @@ module RSMP
32
62
  send_generated_message message
33
63
  end
34
64
 
65
+ def send_changed_site_status
66
+ bits = @site.site_aggregated_status
67
+ return if bits == @last_site_status
68
+ return if connected? && !ready?
69
+
70
+ result = send_site_aggregated_status
71
+ @last_site_status = bits if result.success?
72
+ result
73
+ end
74
+
75
+ def send_site_aggregated_status(m_id: nil)
76
+ message = RSMP::AggregatedStatus.new(
77
+ 'mId' => m_id, 'aSTS' => clock.to_s, 'se' => @site.site_aggregated_status
78
+ )
79
+ apply_nts_message_attributes message
80
+ result = send_generated_message message
81
+ @last_site_status = message.attributes['se'].dup if result.success?
82
+ result
83
+ end
84
+
35
85
  def process_aggregated_status_request(message)
36
86
  log "Received #{message.type}", message: message, level: :log
87
+ if core_3_3_0_or_newer?
88
+ acknowledge message
89
+ return send_site_aggregated_status
90
+ end
91
+
37
92
  component_id = message.attributes['cId']
38
93
  component = @site.find_component component_id
39
94
  acknowledge message
@@ -6,14 +6,21 @@ module RSMP
6
6
  def send_active_alarms
7
7
  @site.components.each_pair do |_c_id, component|
8
8
  component.alarms.each_pair do |_alarm_code, alarm_state|
9
- if alarm_state.active
10
- alarm = AlarmIssue.new(alarm_state.to_hash.merge('aSp' => 'Issue'))
11
- send_generated_message alarm
12
- end
9
+ next unless alarm_state.active || core_3_3_0_or_newer?
10
+
11
+ alarm = AlarmIssue.new(alarm_state.to_hash.merge('aSp' => 'Issue'))
12
+ next if core_3_3_0_or_newer? && !sxl_message_allowed?(alarm)
13
+
14
+ send_generated_message alarm
15
+ @initial_alarm_events << alarm_event_key(alarm) if core_3_3_0_or_newer? && @initial_alarm_events
13
16
  end
14
17
  end
15
18
  end
16
19
 
20
+ def alarm_event_key(message)
21
+ message.attributes.except('mId', 'ntsOId', 'xNId')
22
+ end
23
+
17
24
  def process_alarm(message)
18
25
  case message
19
26
  when AlarmAcknowledge
@@ -49,7 +49,7 @@ module RSMP
49
49
  end
50
50
 
51
51
  def check_required_command_arguments(message)
52
- return unless core_3_3?
52
+ return unless core_3_3_0_or_newer?
53
53
 
54
54
  command_codes(message).each do |command_code|
55
55
  provided = message.attributes['arg'].select { |item| item['cCI'] == command_code }.map { |item| item['n'] }
@@ -93,11 +93,12 @@ module RSMP
93
93
  end
94
94
 
95
95
  def process_command_request(message)
96
- return reject_multiple_command_codes(message) if core_3_3? && multiple_command_codes?(message)
96
+ return reject_multiple_command_codes(message) if core_3_3_0_or_newer? && multiple_command_codes?(message)
97
97
 
98
98
  check_required_command_arguments message
99
99
  component_id = message.attributes['cId']
100
100
  rvs = build_command_rvs(message.attributes['arg'])
101
+ acknowledge message if core_3_3_0_or_newer?
101
102
  execute_commands(message, component_id, rvs)
102
103
 
103
104
  response = CommandResponse.new({
@@ -106,7 +107,7 @@ module RSMP
106
107
  'rvs' => rvs
107
108
  })
108
109
  apply_nts_message_attributes response
109
- acknowledge message
110
+ acknowledge message unless core_3_3_0_or_newer?
110
111
  send_generated_message response
111
112
  end
112
113
  end
@@ -76,8 +76,9 @@ module RSMP
76
76
 
77
77
  def prepare_message_for_buffer(message, core_version: @core_version, for_send: false)
78
78
  return unless site_originated_buffer_candidate? message
79
+ return unless sxl_message_allowed?(message)
79
80
  return false if message.is_a?(RSMP::StatusUpdate) && status_buffer_selectors == false
80
- return false if message.is_a?(RSMP::Alarm) && !receive_alarms?
81
+ return false if message.is_a?(RSMP::Alarm) && !use_alarms?
81
82
 
82
83
  if message.is_a? RSMP::AggregatedStatus
83
84
  prepare_aggregated_status_for_buffer message, core_version: core_version, for_send: for_send
@@ -121,6 +122,10 @@ module RSMP
121
122
  end
122
123
 
123
124
  def flush_buffered_message?(message, queued, index)
125
+ if core_3_3_0_or_newer? && message.is_a?(Alarm) && @initial_alarm_events&.include?(alarm_event_key(message))
126
+ return true
127
+ end
128
+
124
129
  prepared = prepare_message_for_buffer message, core_version: @core_version, for_send: true
125
130
  return true unless prepared
126
131