openc3 5.18.0 → 5.19.0

Sign up to get free protection for your applications and to get access to all the features.
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
  ##################################################