openc3 5.18.0 → 5.19.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -4
  3. data/bin/cstol_converter +14 -14
  4. data/bin/openc3cli +189 -7
  5. data/data/config/_interfaces.yaml +1 -1
  6. data/data/config/command_modifiers.yaml +55 -0
  7. data/data/config/interface_modifiers.yaml +1 -1
  8. data/data/config/param_item_modifiers.yaml +1 -1
  9. data/data/config/parameter_modifiers.yaml +1 -1
  10. data/data/config/plugins.yaml +6 -2
  11. data/data/config/screen.yaml +2 -2
  12. data/data/config/table_manager.yaml +2 -2
  13. data/data/config/tool.yaml +4 -1
  14. data/data/config/widgets.yaml +3 -3
  15. data/ext/openc3/ext/config_parser/config_parser.c +1 -1
  16. data/ext/openc3/ext/packet/packet.c +1 -1
  17. data/ext/openc3/ext/platform/platform.c +3 -3
  18. data/ext/openc3/ext/structure/structure.c +56 -76
  19. data/lib/openc3/accessors/binary_accessor.rb +4 -4
  20. data/lib/openc3/accessors/form_accessor.rb +2 -2
  21. data/lib/openc3/accessors/http_accessor.rb +1 -1
  22. data/lib/openc3/accessors/json_accessor.rb +6 -4
  23. data/lib/openc3/accessors/template_accessor.rb +6 -9
  24. data/lib/openc3/accessors/xml_accessor.rb +1 -1
  25. data/lib/openc3/api/cmd_api.rb +35 -11
  26. data/lib/openc3/api/limits_api.rb +1 -1
  27. data/lib/openc3/config/config_parser.rb +1 -1
  28. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +7 -7
  29. data/lib/openc3/core_ext/array.rb +5 -5
  30. data/lib/openc3/core_ext/exception.rb +9 -2
  31. data/lib/openc3/core_ext/string.rb +2 -2
  32. data/lib/openc3/interfaces/http_server_interface.rb +1 -0
  33. data/lib/openc3/interfaces/interface.rb +1 -1
  34. data/lib/openc3/interfaces/linc_interface.rb +3 -3
  35. data/lib/openc3/io/json_api.rb +11 -6
  36. data/lib/openc3/io/json_rpc.rb +1 -1
  37. data/lib/openc3/logs/buffered_packet_log_writer.rb +3 -3
  38. data/lib/openc3/logs/log_writer.rb +7 -8
  39. data/lib/openc3/logs/packet_log_writer.rb +7 -7
  40. data/lib/openc3/logs/text_log_writer.rb +4 -4
  41. data/lib/openc3/microservices/decom_microservice.rb +19 -4
  42. data/lib/openc3/microservices/interface_microservice.rb +41 -3
  43. data/lib/openc3/microservices/reaction_microservice.rb +2 -2
  44. data/lib/openc3/microservices/trigger_group_microservice.rb +3 -3
  45. data/lib/openc3/migrations/20240915000000_activity_uuid.rb +28 -0
  46. data/lib/openc3/models/activity_model.rb +109 -80
  47. data/lib/openc3/models/auth_model.rb +31 -2
  48. data/lib/openc3/models/cvt_model.rb +11 -5
  49. data/lib/openc3/models/gem_model.rb +8 -8
  50. data/lib/openc3/models/plugin_model.rb +3 -3
  51. data/lib/openc3/models/reducer_model.rb +2 -2
  52. data/lib/openc3/models/scope_model.rb +1 -1
  53. data/lib/openc3/models/sorted_model.rb +4 -4
  54. data/lib/openc3/models/target_model.rb +3 -3
  55. data/lib/openc3/models/tool_config_model.rb +1 -1
  56. data/lib/openc3/models/tool_model.rb +4 -4
  57. data/lib/openc3/models/widget_model.rb +11 -5
  58. data/lib/openc3/operators/operator.rb +5 -3
  59. data/lib/openc3/packets/command_validator.rb +48 -0
  60. data/lib/openc3/packets/commands.rb +6 -14
  61. data/lib/openc3/packets/packet.rb +31 -15
  62. data/lib/openc3/packets/packet_config.rb +10 -9
  63. data/lib/openc3/packets/parsers/packet_parser.rb +3 -3
  64. data/lib/openc3/packets/structure.rb +21 -13
  65. data/lib/openc3/packets/structure_item.rb +33 -47
  66. data/lib/openc3/packets/telemetry.rb +6 -27
  67. data/lib/openc3/script/api_shared.rb +7 -5
  68. data/lib/openc3/script/calendar.rb +2 -2
  69. data/lib/openc3/script/commands.rb +6 -4
  70. data/lib/openc3/script/metadata.rb +2 -2
  71. data/lib/openc3/script/suite.rb +17 -17
  72. data/lib/openc3/streams/serial_stream.rb +2 -3
  73. data/lib/openc3/streams/stream.rb +2 -2
  74. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +10 -10
  75. data/lib/openc3/tools/table_manager/table_manager_core.rb +11 -11
  76. data/lib/openc3/tools/table_manager/table_parser.rb +2 -3
  77. data/lib/openc3/topics/command_decom_topic.rb +2 -1
  78. data/lib/openc3/topics/command_topic.rb +3 -3
  79. data/lib/openc3/topics/decom_interface_topic.rb +2 -2
  80. data/lib/openc3/topics/telemetry_decom_topic.rb +1 -1
  81. data/lib/openc3/utilities/authorization.rb +2 -1
  82. data/lib/openc3/utilities/cli_generator.rb +15 -8
  83. data/lib/openc3/utilities/cosmos_rails_formatter.rb +60 -0
  84. data/lib/openc3/utilities/crc.rb +6 -6
  85. data/lib/openc3/utilities/local_mode.rb +2 -1
  86. data/lib/openc3/utilities/logger.rb +44 -34
  87. data/lib/openc3/utilities/metric.rb +1 -2
  88. data/lib/openc3/utilities/quaternion.rb +18 -18
  89. data/lib/openc3/utilities/target_file.rb +4 -4
  90. data/lib/openc3/version.rb +5 -5
  91. data/lib/openc3/win32/win32_main.rb +2 -2
  92. data/templates/tool_angular/package.json +21 -21
  93. data/templates/tool_react/package.json +10 -10
  94. data/templates/tool_svelte/package.json +11 -11
  95. data/templates/tool_svelte/src/services/openc3-api.js +17 -17
  96. data/templates/tool_vue/package.json +9 -9
  97. data/templates/widget/package.json +6 -7
  98. metadata +5 -2
@@ -47,41 +47,28 @@ module OpenC3
47
47
  start_score = now - 15
48
48
  stop_score = (now + 3660)
49
49
  array = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", start_score, stop_score)
50
- ret_array = Array.new
51
- array.each do |value|
52
- ret_array << ActivityModel.from_json(value, name: name, scope: scope)
53
- end
54
- return ret_array
50
+ return array.map { |value| ActivityModel.from_json(value, name: name, scope: scope) }
55
51
  end
56
52
 
57
53
  # @return [Array|nil] Array up to 100 of this model or empty array if name not found under primary_key
58
54
  def self.get(name:, start:, stop:, scope:, limit: 100)
59
55
  if start > stop
60
- raise ActivityInputError.new "start: #{start} must be before stop: #{stop}"
56
+ raise ActivityInputError.new "start: #{start} must be <= stop: #{stop}"
61
57
  end
62
-
63
58
  array = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", start, stop, :limit => [0, limit])
64
- ret_array = Array.new
65
- array.each do |value|
66
- ret_array << JSON.parse(value, :allow_nan => true, :create_additions => true)
67
- end
68
- return ret_array
59
+ return array.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
69
60
  end
70
61
 
71
62
  # @return [Array<Hash>] Array up to the limit of the models (as Hash objects) stored under the primary key
72
63
  def self.all(name:, scope:, limit: 100)
73
64
  array = Store.zrange("#{scope}#{PRIMARY_KEY}__#{name}", 0, -1, :limit => [0, limit])
74
- ret_array = Array.new
75
- array.each do |value|
76
- ret_array << JSON.parse(value, :allow_nan => true, :create_additions => true)
77
- end
78
- return ret_array
65
+ return array.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
79
66
  end
80
67
 
81
68
  # @return [String|nil] String of the saved json or nil if score not found under primary_key
82
69
  def self.score(name:, score:, scope:)
83
- array = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", score, score, :limit => [0, 1])
84
- array.each do |value|
70
+ value = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", score, score, :limit => [0, 1]).first
71
+ if value
85
72
  return ActivityModel.from_json(value, name: name, scope: scope)
86
73
  end
87
74
  return nil
@@ -93,9 +80,45 @@ module OpenC3
93
80
  end
94
81
 
95
82
  # Remove one member from a sorted set.
96
- # @return [Integer] count of the members removed
97
- def self.destroy(name:, scope:, score:)
98
- result = Store.zremrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", score, score)
83
+ # @return [Integer] count of the members removed, 0 indicates the member was not found
84
+ def self.destroy(name:, scope:, score:, uuid: nil, recurring: nil)
85
+ result = 0
86
+
87
+ # Delete all recurring activities
88
+ if recurring
89
+ activity = self.score(name: name, score: score, scope: scope)
90
+ if activity and activity.recurring['end'] and activity.recurring['uuid']
91
+ json = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", activity.recurring['start'], activity.recurring['end'])
92
+ parsed = json.map { |value| ActivityModel.from_json(value, name: name, scope: scope) }
93
+ parsed.each_with_index do |value, index|
94
+ if value.recurring['uuid'] == uuid
95
+ Store.zrem("#{scope}#{PRIMARY_KEY}__#{name}", json[index])
96
+ result += 1
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ # First find all the activities at the score
103
+ json = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{name}", score, score, :limit => [0, 100])
104
+ parsed = json.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
105
+ parsed.each_with_index do |value, index|
106
+ if uuid
107
+ # If the uuid is given then only delete activities that match the uuid
108
+ if value['uuid'] == uuid
109
+ Store.zrem("#{scope}#{PRIMARY_KEY}__#{name}", json[index])
110
+ result += 1
111
+ break
112
+ end
113
+ else
114
+ # If the uuid is not given (backwards compatibility) then delete all activities
115
+ # at the score that do NOT have a uuid
116
+ next if value['uuid']
117
+ Store.zrem("#{scope}#{PRIMARY_KEY}__#{name}", json[index])
118
+ result += 1
119
+ end
120
+ end
121
+
99
122
  notification = {
100
123
  # start / stop to match SortedModel
101
124
  'data' => JSON.generate({'start' => score}),
@@ -129,27 +152,30 @@ module OpenC3
129
152
  self.new(**json.transform_keys(&:to_sym), name: name, scope: scope)
130
153
  end
131
154
 
132
- attr_reader :start, :stop, :kind, :data, :events, :fulfillment, :recurring
155
+ attr_reader :start, :stop, :kind, :data, :fulfillment, :uuid, :events, :recurring
133
156
 
134
157
  def initialize(
135
- name:,
158
+ name:, # part of Model
136
159
  start:,
137
160
  stop:,
138
161
  kind:,
139
162
  data:,
140
- scope:,
141
- updated_at: 0,
163
+ scope:, # part of Model
164
+ updated_at: 0, # part of Model
142
165
  fulfillment: nil,
166
+ uuid: nil,
143
167
  events: nil,
144
168
  recurring: {}
145
169
  )
146
170
  super("#{scope}#{PRIMARY_KEY}__#{name}", name: name, scope: scope)
171
+ # Validate everything that isn't already in Model
147
172
  set_input(
148
- fulfillment: fulfillment,
149
173
  start: start,
150
174
  stop: stop,
151
175
  kind: kind,
152
176
  data: data,
177
+ fulfillment: fulfillment,
178
+ uuid: uuid,
153
179
  events: events,
154
180
  recurring: recurring,
155
181
  )
@@ -162,19 +188,19 @@ module OpenC3
162
188
  # need to return all the activities that may start before us and verify that we don't overlap them.
163
189
  # Activities are only inserted by @start time so we need to go back to verify we don't overlap existing @stop.
164
190
  # Note: Score is the Seconds since the Unix Epoch: (%s) Number of seconds since 1970-01-01 00:00:00 UTC.
165
- # zrange rev byscore finds activites from in reverse order so the first task is the closest task to the current score.
191
+ # zrange rev byscore finds activities from in reverse order so the first task is the closest task to the current score.
166
192
  # In this case a parameter ignore_score allows the request to ignore that time and skip to the next time
167
193
  # but if nothing is found in the time range we can return nil.
168
194
  #
169
195
  # @param [Integer] ignore_score - should be nil unless you want to ignore a time when doing an update
170
- def validate_time(ignore_score = nil)
196
+ def validate_time(start, stop, ignore_score: nil)
171
197
  # Adding a '(' makes the max value exclusive
172
- array = Store.zrevrangebyscore(@primary_key, "(#{@stop}", @start - MAX_DURATION)
198
+ array = Store.zrevrangebyscore(@primary_key, "(#{stop}", start - MAX_DURATION)
173
199
  array.each do |value|
174
200
  activity = JSON.parse(value, :allow_nan => true, :create_additions => true)
175
201
  if ignore_score == activity['start']
176
202
  next
177
- elsif activity['stop'] > @start
203
+ elsif activity['stop'] > start
178
204
  return activity['start']
179
205
  else
180
206
  return nil
@@ -204,7 +230,7 @@ module OpenC3
204
230
  end
205
231
  if now_f >= start and kind != 'expire'
206
232
  raise ActivityInputError.new "activity must be in the future, current_time: #{now_f} vs #{start}"
207
- elsif duration >= MAX_DURATION and kind != 'expire'
233
+ elsif duration > MAX_DURATION and kind != 'expire'
208
234
  raise ActivityInputError.new "activity can not be longer than #{MAX_DURATION} seconds"
209
235
  elsif duration <= 0
210
236
  raise ActivityInputError.new "start: #{start} must be before stop: #{stop}"
@@ -218,7 +244,7 @@ module OpenC3
218
244
  end
219
245
 
220
246
  # Set the values of the instance, @start, @kind, @data, @events...
221
- def set_input(start:, stop:, kind: nil, data: nil, events: nil, fulfillment: nil, recurring: nil)
247
+ def set_input(start:, stop:, kind: nil, data: nil, uuid: nil, events: nil, fulfillment: nil, recurring: nil)
222
248
  kind = kind.to_s.downcase
223
249
  validate_input(start: start, stop: stop, kind: kind, data: data)
224
250
  @start = start
@@ -226,6 +252,7 @@ module OpenC3
226
252
  @fulfillment = fulfillment.nil? ? false : fulfillment
227
253
  @kind = kind
228
254
  @data = data.nil? ? @data : data
255
+ @uuid = uuid.nil? ? SecureRandom.uuid : uuid
229
256
  @events = events.nil? ? Array.new : events
230
257
  @recurring = recurring.nil? ? @recurring : recurring
231
258
  end
@@ -241,20 +268,22 @@ module OpenC3
241
268
  @recurring['uuid'] = SecureRandom.uuid
242
269
  @recurring['start'] = @start
243
270
  duration = @stop - @start
244
- recurrance = 0
271
+ recurrence = 0
245
272
  case @recurring['span']
246
273
  when 'minutes'
247
- recurrance = @recurring['frequency'].to_i * 60
274
+ recurrence = @recurring['frequency'].to_i * 60
248
275
  when 'hours'
249
- recurrance = @recurring['frequency'].to_i * 3600
276
+ recurrence = @recurring['frequency'].to_i * 3600
250
277
  when 'days'
251
- recurrance = @recurring['frequency'].to_i * 86400
278
+ recurrence = @recurring['frequency'].to_i * 86400
252
279
  end
253
280
 
254
- # Get all the existing events in the recurring time range as well as those before
255
- # the start of the recurring time range to ensure we don't start inside an existing event
256
- existing = Store.zrevrangebyscore(@primary_key, @recurring['end'] - 1, @recurring['start'] - MAX_DURATION)
257
- existing.map! {|value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
281
+ unless overlap
282
+ # Get all the existing events in the recurring time range as well as those before
283
+ # the start of the recurring time range to ensure we don't start inside an existing event
284
+ existing = Store.zrevrangebyscore(@primary_key, @recurring['end'] - 1, @recurring['start'] - MAX_DURATION)
285
+ existing.map! {|value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
286
+ end
258
287
  last_stop = nil
259
288
 
260
289
  # Update @updated_at and add an event assuming it all completes ok
@@ -262,19 +291,21 @@ module OpenC3
262
291
  add_event(status: 'created')
263
292
 
264
293
  Store.multi do |multi|
265
- (@start..@recurring['end']).step(recurrance).each do |start_time|
294
+ (@start..@recurring['end']).step(recurrence).each do |start_time|
266
295
  @start = start_time
267
296
  @stop = start_time + duration
268
297
 
269
298
  if last_stop and @start < last_stop
270
299
  @events.pop # Remove previously created event
271
- raise ActivityOverlapError.new "Recurring activity overlap. Increase recurrance delta or decrease activity duration."
300
+ raise ActivityOverlapError.new "Recurring activity overlap. Increase recurrence delta or decrease activity duration."
272
301
  end
273
- existing.each do |value|
274
- if (@start >= value['start'] and @start < value['stop']) ||
275
- (@stop > value['start'] and @stop <= value['stop'])
276
- @events.pop # Remove previously created event
277
- raise ActivityOverlapError.new "activity overlaps existing at #{value['start']}"
302
+ unless overlap
303
+ existing.each do |value|
304
+ if (@start >= value['start'] and @start < value['stop']) ||
305
+ (@stop > value['start'] and @stop <= value['stop'])
306
+ @events.pop # Remove previously created event
307
+ raise ActivityOverlapError.new "activity overlaps existing at #{value['start']}"
308
+ end
278
309
  end
279
310
  end
280
311
  multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
@@ -284,9 +315,9 @@ module OpenC3
284
315
  notify(kind: 'created')
285
316
  else
286
317
  validate_input(start: @start, stop: @stop, kind: @kind, data: @data)
287
- if !overlap
318
+ unless overlap
288
319
  # If we don't allow overlap we need to validate the time
289
- collision = validate_time()
320
+ collision = validate_time(@start, @stop)
290
321
  unless collision.nil?
291
322
  raise ActivityOverlapError.new "activity overlaps existing at #{collision}"
292
323
  end
@@ -308,20 +339,27 @@ module OpenC3
308
339
  end
309
340
 
310
341
  old_start = @start
311
- set_input(start: start, stop: stop, kind: kind, data: data, events: @events)
312
- @updated_at = Time.now.to_nsec_from_epoch
313
- if !overlap
342
+ old_uuid = @uuid
343
+ unless overlap
314
344
  # If we don't allow overlap we need to validate the time
315
- collision = validate_time(old_start)
345
+ collision = validate_time(start, stop, ignore_score: old_start)
316
346
  unless collision.nil?
317
347
  raise ActivityOverlapError.new "failed to update #{old_start}, no activities can overlap, collision: #{collision}"
318
348
  end
319
349
  end
350
+ set_input(start: start, stop: stop, kind: kind, data: data, events: @events)
351
+ @updated_at = Time.now.to_nsec_from_epoch
320
352
 
321
353
  add_event(status: 'updated')
322
- Store.multi do |multi|
323
- multi.zremrangebyscore(@primary_key, old_start, old_start)
324
- multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
354
+ json = Store.zrangebyscore(@primary_key, old_start, old_start)
355
+ parsed = json.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
356
+ parsed.each_with_index do |value, index|
357
+ if value['uuid'] == old_uuid
358
+ Store.multi do |multi|
359
+ multi.zrem(@primary_key, json[index])
360
+ multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
361
+ end
362
+ end
325
363
  end
326
364
  notify(kind: 'updated', extra: old_start)
327
365
  return @start
@@ -339,9 +377,16 @@ module OpenC3
339
377
  event['message'] = message unless message.nil?
340
378
  @fulfillment = fulfillment.nil? ? @fulfillment : fulfillment
341
379
  @events << event
342
- Store.multi do |multi|
343
- multi.zremrangebyscore(@primary_key, @start, @start)
344
- multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
380
+
381
+ json = Store.zrangebyscore(@primary_key, @start, @start)
382
+ parsed = json.map { |value| JSON.parse(value, :allow_nan => true, :create_additions => true) }
383
+ parsed.each_with_index do |value, index|
384
+ if value['uuid'] == @uuid
385
+ Store.multi do |multi|
386
+ multi.zrem(@primary_key, json[index])
387
+ multi.zadd(@primary_key, @start, JSON.generate(self.as_json(:allow_nan => true)))
388
+ end
389
+ end
345
390
  end
346
391
  notify(kind: 'event')
347
392
  end
@@ -356,24 +401,6 @@ module OpenC3
356
401
  @events << event
357
402
  end
358
403
 
359
- # destroy the activity from the redis database
360
- def destroy(recurring: false)
361
- # Delete all recurring activities
362
- if recurring and @recurring['end'] and @recurring['uuid']
363
- uuid = @recurring['uuid']
364
- array = Store.zrangebyscore("#{scope}#{PRIMARY_KEY}__#{@name}", @recurring['start'], @recurring['end'])
365
- array.each do |value|
366
- model = ActivityModel.from_json(value, name: @name, scope: @scope)
367
- if model.recurring['uuid'] == uuid
368
- Store.zremrangebyscore(@primary_key, model.start, model.start)
369
- end
370
- end
371
- else
372
- Store.zremrangebyscore(@primary_key, @start, @start)
373
- end
374
- notify(kind: 'deleted')
375
- end
376
-
377
404
  # update the redis stream / timeline topic that something has changed
378
405
  def notify(kind:, extra: nil)
379
406
  notification = {
@@ -395,12 +422,14 @@ module OpenC3
395
422
  {
396
423
  'name' => @name,
397
424
  'updated_at' => @updated_at,
398
- 'fulfillment' => @fulfillment,
399
425
  'start' => @start,
400
426
  'stop' => @stop,
401
427
  'kind' => @kind,
402
- 'events' => @events,
403
428
  'data' => @data.as_json(*a),
429
+ 'scope' => @scope,
430
+ 'fulfillment' => @fulfillment,
431
+ 'uuid' => @uuid,
432
+ 'events' => @events,
404
433
  'recurring' => @recurring.as_json(*a)
405
434
  }
406
435
  end
@@ -21,15 +21,22 @@
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
23
  require 'digest'
24
+ require 'securerandom'
24
25
  require 'openc3/utilities/store'
25
26
 
26
27
  module OpenC3
27
28
  class AuthModel
28
29
  PRIMARY_KEY = 'OPENC3__TOKEN'
30
+ SESSIONS_KEY = 'OPENC3__SESSIONS'
29
31
 
30
32
  TOKEN_CACHE_TIMEOUT = 5
33
+ SESSION_CACHE_TIMEOUT = 5
31
34
  @@token_cache = nil
32
35
  @@token_cache_time = nil
36
+ @@session_cache = nil
37
+ @@session_cache_time = nil
38
+
39
+ MIN_TOKEN_LENGTH = 8
33
40
 
34
41
  def self.set?(key = PRIMARY_KEY)
35
42
  Store.exists(key) == 1
@@ -38,14 +45,23 @@ module OpenC3
38
45
  def self.verify(token)
39
46
  return false if token.nil? or token.empty?
40
47
 
48
+ time = Time.now
49
+ return true if @@session_cache and (time - @@session_cache_time) < SESSION_CACHE_TIMEOUT and @@session_cache[token]
41
50
  token_hash = hash(token)
42
- return true if @@token_cache and (Time.now - @@token_cache_time) < TOKEN_CACHE_TIMEOUT and @@token_cache == token_hash
51
+ return true if @@token_cache and (time - @@token_cache_time) < TOKEN_CACHE_TIMEOUT and @@token_cache == token_hash
52
+
53
+ # Check sessions
54
+ @@session_cache = Store.hgetall(SESSIONS_KEY)
55
+ @@session_cache_time = time
56
+ return true if @@session_cache[token]
43
57
 
58
+ # Check Direct password
44
59
  @@token_cache = Store.get(PRIMARY_KEY)
45
- @@token_cache_time = Time.now
60
+ @@token_cache_time = time
46
61
  return true if @@token_cache == token_hash
47
62
 
48
63
  # Handle a service password - Generally only used by ScriptRunner
64
+ # TODO: Replace this with temporary service tokens
49
65
  service_password = ENV['OPENC3_SERVICE_PASSWORD']
50
66
  return true if service_password and service_password == token
51
67
 
@@ -54,6 +70,7 @@ module OpenC3
54
70
 
55
71
  def self.set(token, old_token, key = PRIMARY_KEY)
56
72
  raise "token must not be nil or empty" if token.nil? or token.empty?
73
+ raise "token must be at least 8 characters" if token.length < MIN_TOKEN_LENGTH
57
74
 
58
75
  if set?(key)
59
76
  raise "old_token must not be nil or empty" if old_token.nil? or old_token.empty?
@@ -62,6 +79,18 @@ module OpenC3
62
79
  Store.set(key, hash(token))
63
80
  end
64
81
 
82
+ def self.generate_session
83
+ token = SecureRandom.urlsafe_base64(nil, false)
84
+ Store.hset(SESSIONS_KEY, token, Time.now.iso8601)
85
+ return token
86
+ end
87
+
88
+ def self.logout
89
+ Store.del(SESSIONS_KEY)
90
+ @@sessions_cache = nil
91
+ @@sessions_cache_time = nil
92
+ end
93
+
65
94
  def self.hash(token)
66
95
  Digest::SHA2.hexdigest token
67
96
  end
@@ -100,15 +100,21 @@ module OpenC3
100
100
  result, types = self._handle_item_override(target_name, packet_name, item_name, type: type, cache_timeout: cache_timeout, scope: scope)
101
101
  return result if result
102
102
  hash = get(target_name: target_name, packet_name: packet_name, cache_timeout: cache_timeout, scope: scope)
103
- hash.values_at(*types).each do |result|
104
- if result
103
+ hash.values_at(*types).each do |cvt_value|
104
+ if cvt_value
105
105
  if type == :FORMATTED or type == :WITH_UNITS
106
- return result.to_s
106
+ return cvt_value.to_s
107
107
  end
108
- return result
108
+ return cvt_value
109
109
  end
110
110
  end
111
- return nil
111
+ # RECEIVED_COUNT is a special case where it is 0 if it doesn't exist
112
+ # This allows scripts to check against the value to see if the packet was ever received
113
+ if item_name == "RECEIVED_COUNT"
114
+ return 0
115
+ else
116
+ return nil
117
+ end
112
118
  end
113
119
 
114
120
  # Return all item values and limit state from the CVT
@@ -90,16 +90,16 @@ module OpenC3
90
90
  Gem.sources = [rubygems_url] if rubygems_url
91
91
  Gem.done_installing_hooks.clear
92
92
  begin
93
- # Look for local gems only first, this avoids lengthly timeouts when checking rubygems in airgap env
93
+ # Look for local gems only first, this avoids lengthy timeouts when checking rubygems in airgap env
94
94
  Gem.install(gem_file_path, "> 0.pre", build_args: ['--no-document'], prerelease: true, domain: :local)
95
- rescue Gem::Exception => err
95
+ rescue Gem::Exception => e
96
96
  # If there is a failure look for both local and remote gems
97
97
  Gem.install(gem_file_path, "> 0.pre", build_args: ['--no-document'], prerelease: true, domain: :both)
98
98
  end
99
- rescue => err
100
- message = "Gem file #{gem_file_path} error installing to #{ENV['GEM_HOME']}\n#{err.formatted}"
99
+ rescue => e
100
+ message = "Gem file #{gem_file_path} error installing to #{ENV['GEM_HOME']}\n#{e.formatted}"
101
101
  Logger.error message
102
- raise err
102
+ raise e
103
103
  end
104
104
 
105
105
  def self.destroy(name, log_and_raise_needed_errors: true)
@@ -114,9 +114,9 @@ module OpenC3
114
114
  else
115
115
  begin
116
116
  Gem::Uninstaller.new(gem_name, {:version => version, :force => true}).uninstall
117
- rescue => err
118
- Logger.error "Gem file #{name} error uninstalling\n#{err.formatted}"
119
- raise err
117
+ rescue => e
118
+ Logger.error "Gem file #{name} error uninstalling\n#{e.formatted}"
119
+ raise e
120
120
  end
121
121
  end
122
122
  end
@@ -69,7 +69,7 @@ module OpenC3
69
69
  end
70
70
 
71
71
  # Called by the PluginsController to parse the plugin variables
72
- # Doesn't actaully create the plugin during the phase
72
+ # Doesn't actually create the plugin during the phase
73
73
  def self.install_phase1(gem_file_path, existing_variables: nil, existing_plugin_txt_lines: nil, process_existing: false, scope:, validate_only: false)
74
74
  gem_name = File.basename(gem_file_path).split("__")[0]
75
75
 
@@ -140,8 +140,8 @@ module OpenC3
140
140
  end
141
141
 
142
142
  # Called by the PluginsController to create the plugin
143
- # Because this uses ERB it must be run in a seperate process from the API to
144
- # prevent corruption and single require problems in the current proces
143
+ # Because this uses ERB it must be run in a separate process from the API to
144
+ # prevent corruption and single require problems in the current process
145
145
  def self.install_phase2(plugin_hash, scope:, gem_file_path: nil, validate_only: false)
146
146
  # Register plugin to aid in uninstall if install fails
147
147
  plugin_hash.delete("existing_plugin_txt_lines")
@@ -17,14 +17,14 @@
17
17
  # All changes Copyright 2022, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
- # This file may also be used under the terms of a commercial license
20
+ # This file may also be used under the terms of a commercial license
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
23
  require 'openc3/utilities/store'
24
24
 
25
25
  module OpenC3
26
26
  # Tracks the files which are being stored in buckets for data reduction purposes.
27
- # Files are stored in a Redis set by spliting their filenames and storing in
27
+ # Files are stored in a Redis set by splitting their filenames and storing in
28
28
  # a set named SCOPE__TARGET__reducer__TYPE, e.g. DEFAULT__INST__reducer__decom
29
29
  # Where TYPE can be 'decom', 'minute', or 'hour'. 'day' is not necessary because
30
30
  # day is the final reduction state. As files are reduced they are removed from
@@ -110,7 +110,7 @@ module OpenC3
110
110
  end
111
111
 
112
112
  def create(update: false, force: false, queued: false)
113
- # Ensure there are no "." in the scope name - prevents gems accidently becoming scope names
113
+ # Ensure there are no "." in the scope name - prevents gems accidentally becoming scope names
114
114
  raise "Invalid scope name: #{@name}" if @name !~ /^[a-zA-Z0-9_-]+$/
115
115
  @name = @name.upcase
116
116
  @scope = @name # Ensure @scope matches @name
@@ -34,15 +34,15 @@ module OpenC3
34
34
  class SortedOverlapError < SortedError; end
35
35
 
36
36
  class SortedModel < Model
37
- SORTED_TYPE = 'sorted'.freeze # To be overriden by base class
38
- PRIMARY_KEY = '__SORTED'.freeze # To be overriden by base class
37
+ SORTED_TYPE = 'sorted'.freeze # To be overridden by base class
38
+ PRIMARY_KEY = '__SORTED'.freeze # To be overridden by base class
39
39
 
40
- # MUST be overriden by any subclasses
40
+ # MUST be overridden by any subclasses
41
41
  def self.pk(scope)
42
42
  return "#{scope}#{PRIMARY_KEY}"
43
43
  end
44
44
 
45
- # MUST be overriden by any subclasses
45
+ # MUST be overridden by any subclasses
46
46
  def self.notify(scope:, kind:, start:, stop: nil)
47
47
  # Do nothing by default
48
48
  end
@@ -109,7 +109,7 @@ module OpenC3
109
109
  modified_targets = Bucket.getClient().list_files(bucket: ENV['OPENC3_CONFIG_BUCKET'], path: "DEFAULT/targets_modified/", only_directories: true)
110
110
  modified_targets.each do |target_name|
111
111
  # A target could have been deleted without removing the modified files
112
- # Thus we have to check for the existance of the target_name key
112
+ # Thus we have to check for the existence of the target_name key
113
113
  if targets.has_key?(target_name)
114
114
  targets[target_name]['modified'] = true
115
115
  end
@@ -250,7 +250,7 @@ module OpenC3
250
250
  (items - found_items).each do |item|
251
251
  not_found << "'#{target_name} #{packet_name} #{item}'"
252
252
  end
253
- # 'does not exist' not gramatically correct but we use it in every other exception
253
+ # 'does not exist' not grammatically correct but we use it in every other exception
254
254
  raise "Item(s) #{not_found.join(', ')} does not exist"
255
255
  end
256
256
  found
@@ -1121,7 +1121,7 @@ module OpenC3
1121
1121
  end
1122
1122
  end
1123
1123
  # If there are any topics (packets) left over that haven't been
1124
- # explictly handled above, spawn another microservice
1124
+ # explicitly handled above, spawn another microservice
1125
1125
  if all_topics.length > 0
1126
1126
  instance = nil
1127
1127
  instance = deploy_count unless deploy_count == 0
@@ -25,7 +25,7 @@ require 'openc3/utilities/local_mode'
25
25
  module OpenC3
26
26
  class ToolConfigModel
27
27
  def self.config_tool_names(scope: $openc3_scope)
28
- cursor, keys = Store.scan(0, match: "#{scope}__config__*", type: 'hash', count: 100)
28
+ _, keys = Store.scan(0, match: "#{scope}__config__*", type: 'hash', count: 100)
29
29
  # Just return the tool name that is used in the other APIs
30
30
  return keys.map! { |key| key.split('__')[2] }.sort
31
31
  end
@@ -96,7 +96,7 @@ module OpenC3
96
96
  def self.set_position(name:, position:, scope:)
97
97
  moving = from_json(get(name: name, scope: scope), scope: scope)
98
98
  old_pos = moving.position
99
- new_pos = Integer(position)
99
+ new_pos = position
100
100
  direction = :down
101
101
  if (old_pos == new_pos)
102
102
  return # we're not doing anything
@@ -225,7 +225,7 @@ module OpenC3
225
225
  @shown = ConfigParser.handle_true_false(parameters[0])
226
226
  when 'POSITION'
227
227
  parser.verify_num_parameters(1, 1, "POSITION <value>")
228
- @position = parameters[0].to_i
228
+ @position = parameters[0].to_f
229
229
  when 'DISABLE_ERB'
230
230
  # 0 to unlimited parameters
231
231
  @disable_erb ||= []
@@ -274,8 +274,8 @@ module OpenC3
274
274
  ConfigTopic.write({ kind: 'deleted', type: 'tool', name: @folder_name, plugin: @plugin }, scope: @scope)
275
275
  end
276
276
  end
277
- rescue Exception => error
278
- Logger.error("Error undeploying tool model #{@name} in scope #{@scope} due to #{error}")
277
+ rescue Exception => e
278
+ Logger.error("Error undeploying tool model #{@name} in scope #{@scope} due to #{e}")
279
279
  end
280
280
 
281
281
  ##################################################