openc3-cosmos-cfdp 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +18 -0
  3. data/README.md +181 -0
  4. data/Rakefile +40 -0
  5. data/lib/cfdp.rb +283 -0
  6. data/lib/cfdp_api.rb +204 -0
  7. data/microservices/CFDP/Gemfile +37 -0
  8. data/microservices/CFDP/Rakefile +6 -0
  9. data/microservices/CFDP/app/controllers/application_controller.rb +46 -0
  10. data/microservices/CFDP/app/controllers/cfdp_controller.rb +222 -0
  11. data/microservices/CFDP/app/models/cfdp_checksum.rb +52 -0
  12. data/microservices/CFDP/app/models/cfdp_crc_checksum.rb +41 -0
  13. data/microservices/CFDP/app/models/cfdp_mib.rb +613 -0
  14. data/microservices/CFDP/app/models/cfdp_model.rb +25 -0
  15. data/microservices/CFDP/app/models/cfdp_null_checksum.rb +29 -0
  16. data/microservices/CFDP/app/models/cfdp_pdu.rb +202 -0
  17. data/microservices/CFDP/app/models/cfdp_receive_transaction.rb +590 -0
  18. data/microservices/CFDP/app/models/cfdp_source_transaction.rb +449 -0
  19. data/microservices/CFDP/app/models/cfdp_topic.rb +58 -0
  20. data/microservices/CFDP/app/models/cfdp_transaction.rb +188 -0
  21. data/microservices/CFDP/app/models/cfdp_user.rb +601 -0
  22. data/microservices/CFDP/bin/rails +4 -0
  23. data/microservices/CFDP/bin/rake +4 -0
  24. data/microservices/CFDP/bin/setup +25 -0
  25. data/microservices/CFDP/config/application.rb +55 -0
  26. data/microservices/CFDP/config/boot.rb +4 -0
  27. data/microservices/CFDP/config/credentials.yml.enc +1 -0
  28. data/microservices/CFDP/config/environment.rb +5 -0
  29. data/microservices/CFDP/config/environments/development.rb +53 -0
  30. data/microservices/CFDP/config/environments/production.rb +75 -0
  31. data/microservices/CFDP/config/environments/test.rb +50 -0
  32. data/microservices/CFDP/config/initializers/application_controller_renderer.rb +8 -0
  33. data/microservices/CFDP/config/initializers/backtrace_silencers.rb +7 -0
  34. data/microservices/CFDP/config/initializers/cfdp_initializer.rb +26 -0
  35. data/microservices/CFDP/config/initializers/cors.rb +16 -0
  36. data/microservices/CFDP/config/initializers/filter_parameter_logging.rb +8 -0
  37. data/microservices/CFDP/config/initializers/inflections.rb +16 -0
  38. data/microservices/CFDP/config/initializers/mime_types.rb +4 -0
  39. data/microservices/CFDP/config/initializers/wrap_parameters.rb +9 -0
  40. data/microservices/CFDP/config/locales/en.yml +29 -0
  41. data/microservices/CFDP/config/puma.rb +38 -0
  42. data/microservices/CFDP/config/routes.rb +16 -0
  43. data/microservices/CFDP/config.ru +5 -0
  44. data/microservices/CFDP/init.sh +9 -0
  45. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_ack.rb +82 -0
  46. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_enum.rb +237 -0
  47. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_eof.rb +87 -0
  48. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_file_data.rb +98 -0
  49. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_finished.rb +114 -0
  50. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_keep_alive.rb +65 -0
  51. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_metadata.rb +116 -0
  52. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_nak.rb +91 -0
  53. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_prompt.rb +60 -0
  54. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_tlv.rb +291 -0
  55. data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_user_ops.rb +749 -0
  56. data/microservices/CFDP/public/robots.txt +1 -0
  57. data/microservices/CFDP/spec/models/cfdp_pdu_ack_spec.rb +114 -0
  58. data/microservices/CFDP/spec/models/cfdp_pdu_eof_spec.rb +159 -0
  59. data/microservices/CFDP/spec/models/cfdp_pdu_file_data_spec.rb +76 -0
  60. data/microservices/CFDP/spec/models/cfdp_pdu_finished_spec.rb +192 -0
  61. data/microservices/CFDP/spec/models/cfdp_pdu_keep_alive_spec.rb +69 -0
  62. data/microservices/CFDP/spec/models/cfdp_pdu_metadata_spec.rb +346 -0
  63. data/microservices/CFDP/spec/models/cfdp_pdu_nak_spec.rb +126 -0
  64. data/microservices/CFDP/spec/models/cfdp_pdu_prompt_spec.rb +94 -0
  65. data/microservices/CFDP/spec/models/cfdp_pdu_spec.rb +111 -0
  66. data/microservices/CFDP/spec/rails_helper.rb +71 -0
  67. data/microservices/CFDP/spec/requests/cfdp_spec.rb +1965 -0
  68. data/microservices/CFDP/spec/spec_helper.rb +200 -0
  69. data/plugin.txt +67 -0
  70. data/targets/CFDPTEST/cmd_tlm/cmd.txt +5 -0
  71. data/targets/CFDPTEST/cmd_tlm/tlm.txt +4 -0
  72. data/targets/CFDPTEST/procedures/cfdp_test_suite.rb +130 -0
  73. data/targets/CFDPTEST/target.txt +4 -0
  74. metadata +118 -0
@@ -0,0 +1,613 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2023 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # Licensed for Evaluation and Educational Use
7
+ #
8
+ # This file may only be used commercially under the terms of a commercial license
9
+ # purchased from OpenC3, Inc.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
+ #
15
+ # The development of this software was funded in-whole or in-part by MethaneSAT LLC.
16
+
17
+ # Table 8-2: Remote Entity Configuration Information
18
+ # Remote entity ID
19
+ # Protocol version number
20
+ # UT address
21
+ # Positive ACK timer interval
22
+ # NAK timer interval
23
+ # Keep Alive interval
24
+ # Immediate NAK mode enabled
25
+ # Default transmission mode
26
+ # Transaction closure requested
27
+ # Check limit
28
+ # Default type of checksum to calculate for all file transmission to this remote entity
29
+ # Disposition of incomplete received file on transaction cancellation
30
+ # CRCs required on transmission
31
+ # Maximum file segment length
32
+ # Keep Alive discrepancy limit
33
+ # Positive ACK timer expiration limit
34
+ # NAK timer expiration limit
35
+ # Transaction inactivity limit
36
+ # Start of transmission opportunity
37
+ # End of transmission opportunity
38
+ # Start of reception opportunity
39
+ # End of reception opportunity
40
+
41
+ require 'openc3/models/microservice_model'
42
+ require 'openc3/utilities/bucket'
43
+ require 'openc3/utilities/logger'
44
+ require 'openc3/config/config_parser'
45
+ require 'tempfile'
46
+ require 'fileutils'
47
+ require 'json'
48
+
49
+ class Tempfile
50
+ def persist(filename)
51
+ FileUtils.mv(self.path, filename)
52
+ ObjectSpace.undefine_finalizer(self)
53
+ end
54
+ end
55
+
56
+ class CfdpMib
57
+
58
+ KNOWN_FAULT_TYPES = [
59
+ "ACK_LIMIT_REACHED",
60
+ "KEEP_ALIVE_LIMIT_REACHED",
61
+ "INVALID_TRANSMISSION_MODE",
62
+ "FILESTORE_REJECTION",
63
+ "FILE_CHECKSUM_FAILURE",
64
+ "FILE_SIZE_ERROR",
65
+ "NAK_LIMIT_REACHED",
66
+ "INACTIVITY_DETECTED",
67
+ "INVALID_FILE_STRUCTURE",
68
+ "CHECK_LIMIT_REACHED",
69
+ "UNSUPPORTED_CHECKSUM_TYPE",
70
+ ]
71
+
72
+ KNOWN_FAULT_RESPONSES = [
73
+ "ISSUE_NOTICE_OF_CANCELLATION",
74
+ "ISSUE_NOTICE_OF_SUSPENSION",
75
+ "IGNORE_ERROR",
76
+ "ABANDON_TRANSACTION",
77
+ ]
78
+
79
+ KNOWN_FIELD_NAMES = [
80
+ 'protocol_version_number',
81
+ 'cmd_info',
82
+ 'ack_timer_interval',
83
+ 'nak_timer_interval',
84
+ 'keep_alive_interval',
85
+ 'check_interval',
86
+ 'immediate_nak_mode',
87
+ 'default_transmission_mode',
88
+ 'transaction_closure_requested',
89
+ 'check_limit',
90
+ 'default_checksum_type',
91
+ 'incomplete_file_disposition',
92
+ 'crcs_required',
93
+ 'maximum_file_segment_length',
94
+ 'keep_alive_discrepancy_limit',
95
+ 'ack_timer_expiration_limit',
96
+ 'nak_timer_expiration_limit',
97
+ 'transaction_inactivity_limit',
98
+ 'entity_id_length',
99
+ 'sequence_number_length',
100
+ 'enable_acks',
101
+ 'enable_keep_alive',
102
+ 'enable_finished',
103
+ 'enable_eof_nak',
104
+ 'tlm_info',
105
+ 'eof_sent_indication',
106
+ 'eof_recv_indication',
107
+ 'file_segment_recv_indication',
108
+ 'transaction_finished_indication',
109
+ 'suspended_indication',
110
+ 'resume_indication',
111
+ 'fault_handler'
112
+ ]
113
+
114
+ @@source_entity_id = 0
115
+ @@entities = {}
116
+ @@bucket = nil
117
+ @@root_path = "/"
118
+ @@transactions = {}
119
+
120
+ def self.transactions
121
+ @@transactions
122
+ end
123
+
124
+ def self.entity(entity_id)
125
+ return @@entities[entity_id]
126
+ end
127
+
128
+ def self.source_entity_id=(id)
129
+ @@source_entity_id = id
130
+ end
131
+
132
+ def self.source_entity_id
133
+ @@source_entity_id
134
+ end
135
+
136
+ def self.source_entity
137
+ return @@entities[@@source_entity_id]
138
+ end
139
+
140
+ def self.bucket=(bucket)
141
+ @@bucket = bucket
142
+ end
143
+
144
+ def self.bucket
145
+ return @@bucket
146
+ end
147
+
148
+ def self.root_path=(root_path)
149
+ @@root_path = root_path
150
+ end
151
+
152
+ def self.root_path
153
+ @@root_path
154
+ end
155
+
156
+ def self.define_entity(entity_id)
157
+ entity_id = Integer(entity_id)
158
+ entity = {}
159
+ entity['id'] = entity_id
160
+
161
+ # Remote Entity Settings
162
+ # Blue Book Version 5 upped this number to 1
163
+ entity['protocol_version_number'] = 1
164
+ entity['cmd_info'] = nil
165
+ entity['ack_timer_interval'] = 600
166
+ entity['nak_timer_interval'] = 600
167
+ entity['keep_alive_interval'] = 600
168
+ entity['check_interval'] = 600
169
+ entity['immediate_nak_mode'] = true
170
+ entity['default_transmission_mode'] = 'UNACKNOWLEDGED'
171
+ entity['transaction_closure_requested'] = "CLOSURE_REQUESTED"
172
+ entity['default_checksum_type'] = 0
173
+ entity['incomplete_file_disposition'] = "DISCARD"
174
+ entity['crcs_required'] = true
175
+ entity['maximum_file_segment_length'] = 1024
176
+ entity['keep_alive_discrepancy_limit'] = entity['maximum_file_segment_length'] * 1000
177
+ entity['ack_timer_expiration_limit'] = 1
178
+ entity['nak_timer_expiration_limit'] = 1
179
+ entity['transaction_inactivity_limit'] = 1
180
+ entity['check_limit'] = 1
181
+ entity['entity_id_length'] = 0 # 0 = 1 byte
182
+ entity['sequence_number_length'] = 0 # 0 = 1 byte
183
+ entity['enable_acks'] = true
184
+ entity['enable_keep_alive'] = true
185
+ entity['enable_finished'] = true
186
+ entity['enable_eof_nak'] = false
187
+
188
+ # Local Entity Settings
189
+ entity['tlm_info'] = []
190
+ entity['eof_sent_indication'] = true
191
+ entity['eof_recv_indication'] = true
192
+ entity['file_segment_recv_indication'] = true
193
+ entity['transaction_finished_indication'] = true
194
+ entity['suspended_indication'] = true
195
+ entity['resume_indication'] = true
196
+ entity['fault_handler'] = {}
197
+ entity['fault_handler']["ACK_LIMIT_REACHED"] = "IGNORE_ERROR"
198
+ entity['fault_handler']["KEEP_ALIVE_LIMIT_REACHED"] = "IGNORE_ERROR"
199
+ entity['fault_handler']["INVALID_TRANSMISSION_MODE"] = "IGNORE_ERROR"
200
+ entity['fault_handler']["FILESTORE_REJECTION"] = "IGNORE_ERROR"
201
+ entity['fault_handler']["FILE_CHECKSUM_FAILURE"] = "IGNORE_ERROR"
202
+ entity['fault_handler']["FILE_SIZE_ERROR"] = "IGNORE_ERROR"
203
+ entity['fault_handler']["NAK_LIMIT_REACHED"] = "IGNORE_ERROR"
204
+ entity['fault_handler']["INACTIVITY_DETECTED"] = "ISSUE_NOTICE_OF_CANCELLATION"
205
+ entity['fault_handler']["INVALID_FILE_STRUCTURE"] = "IGNORE_ERROR"
206
+ entity['fault_handler']["CHECK_LIMIT_REACHED"] = "IGNORE_ERROR"
207
+ entity['fault_handler']["UNSUPPORTED_CHECKSUM_TYPE"] = "IGNORE_ERROR"
208
+
209
+ # TODO: Use interface connected? to limit opportunities?
210
+ @@entities[entity_id] = entity
211
+ return entity
212
+ end
213
+
214
+ def self.set_entity_value(entity_id, field_name, value)
215
+ field_name = field_name.downcase
216
+ entity_id = Integer(entity_id)
217
+ raise "Unknown OPTION #{field_name}" unless KNOWN_FIELD_NAMES.include?(field_name)
218
+ case field_name
219
+ when 'tlm_info'
220
+ if value.length == 3
221
+ @@entities[entity_id][field_name] << value
222
+ else
223
+ raise "Invalid tlm_info: #{value}"
224
+ end
225
+ when 'cmd_info'
226
+ if value.length == 3
227
+ @@entities[entity_id][field_name] = value
228
+ else
229
+ raise "Invalid cmd_info: #{value}"
230
+ end
231
+ else
232
+ @@entities[entity_id][field_name] = value
233
+ end
234
+ end
235
+
236
+ def self.get_source_file(source_file_name)
237
+ file_name = File.join(@@root_path, source_file_name)
238
+ if self.bucket
239
+ file = Tempfile.new
240
+ OpenC3::Bucket.getClient().get_object(bucket: self.bucket, key: file_name, path: file.path)
241
+ else
242
+ file = File.open(file_name, 'rb')
243
+ end
244
+ file
245
+ rescue Errno::ENOENT => error
246
+ OpenC3::Logger.error(error.message, scope: ENV['OPENC3_SCOPE'])
247
+ nil
248
+ end
249
+
250
+ def self.complete_source_file(file)
251
+ file.close
252
+ end
253
+
254
+ def self.put_destination_file(destination_filename, tmp_file)
255
+ file_name = File.join(@@root_path, destination_filename)
256
+ if self.bucket
257
+ OpenC3::Bucket.getClient().put_object(bucket: self.bucket, key: file_name, body: tmp_file.open.read)
258
+ else
259
+ file_name = File.join(@@root_path, destination_filename)
260
+ tmp_file.persist(file_name)
261
+ end
262
+ tmp_file.unlink
263
+ return true
264
+ rescue
265
+ # Something went wrong so return false
266
+ return false
267
+ end
268
+
269
+ def self.filestore_request(action_code, first_file_name, second_file_name)
270
+ # Apply root path
271
+ first_file_name = File.join(@@root_path, first_file_name.to_s)
272
+ second_file_name = File.join(@@root_path, second_file_name.to_s) if second_file_name
273
+
274
+ # Handle file path safety
275
+ first_file_name = File.absolute_path(first_file_name)
276
+ second_file_name = File.absolute_path(second_file_name) if second_file_name
277
+ if (first_file_name.index(@@root_path) != 0) or (second_file_name and second_file_name.index(@@root_path) != 0)
278
+ return "NOT_ALLOWED", "Dangerous filename"
279
+ end
280
+
281
+ status_code = nil
282
+ filestore_message = nil
283
+ begin
284
+ case action_code
285
+ when "CREATE_FILE"
286
+ if self.bucket
287
+ OpenC3::Bucket.getClient().put_object(bucket: self.bucket, key: first_file_name, body: '')
288
+ else
289
+ FileUtils.touch(first_file_name)
290
+ end
291
+ status_code = "SUCCESSFUL"
292
+
293
+ when "DELETE_FILE"
294
+ if self.bucket
295
+ client = OpenC3::Bucket.getClient()
296
+ if client.check_object(bucket: self.bucket, key: first_file_name)
297
+ client.delete_object(bucket: self.bucket, key: first_file_name)
298
+ status_code = "SUCCESSFUL"
299
+ else
300
+ status_code = "FILE_DOES_NOT_EXIST"
301
+ end
302
+ else
303
+ if File.exist?(first_file_name)
304
+ FileUtils.rm(first_file_name)
305
+ status_code = "SUCCESSFUL"
306
+ else
307
+ status_code = "FILE_DOES_NOT_EXIST"
308
+ end
309
+ end
310
+
311
+ when "RENAME_FILE"
312
+ if self.bucket
313
+ client = OpenC3::Bucket.getClient()
314
+ if client.check_object(bucket: self.bucket, key: second_file_name)
315
+ status_code = "NEW_FILE_ALREADY_EXISTS"
316
+ elsif not client.check_object(bucket: self.bucket, key: first_file_name)
317
+ status_code = "OLD_FILE_DOES_NOT_EXIST"
318
+ else
319
+ temp = Tempfile.new
320
+ client.get_object(bucket: self.bucket, key: first_file_name, path: temp.path)
321
+ client.put_object(bucket: self.bucket, key: second_file_name, body: temp.read)
322
+ client.delete_object(bucket: self.bucket, key: first_file_name)
323
+ temp.unlink
324
+ status_code = "SUCCESSFUL"
325
+ end
326
+ else
327
+ if File.exist?(second_file_name)
328
+ status_code = "NEW_FILE_ALREADY_EXISTS"
329
+ elsif not File.exist?(first_file_name)
330
+ status_code = "OLD_FILE_DOES_NOT_EXIST"
331
+ else
332
+ FileUtils.mv(first_file_name, second_file_name)
333
+ status_code = "SUCCESSFUL"
334
+ end
335
+ end
336
+
337
+ when "APPEND_FILE"
338
+ if self.bucket
339
+ client = OpenC3::Bucket.getClient()
340
+ if not client.check_object(bucket: self.bucket, key: first_file_name)
341
+ status_code = "FILE_1_DOES_NOT_EXIST"
342
+ elsif not client.check_object(bucket: self.bucket, key: second_file_name)
343
+ status_code = "FILE_2_DOES_NOT_EXIST"
344
+ else
345
+ temp1 = Tempfile.new
346
+ temp2 = Tempfile.new
347
+ client.get_object(bucket: self.bucket, key: first_file_name, path: temp1.path)
348
+ client.get_object(bucket: self.bucket, key: second_file_name, path: temp2.path)
349
+ client.put_object(bucket: self.bucket, key: first_file_name, body: temp1.read + temp2.read)
350
+ temp1.unlink
351
+ temp2.unlink
352
+ status_code = "SUCCESSFUL"
353
+ end
354
+ else
355
+ if not File.exist?(first_file_name)
356
+ status_code = "FILE_1_DOES_NOT_EXIST"
357
+ elsif not File.exist?(second_file_name)
358
+ status_code = "FILE_2_DOES_NOT_EXIST"
359
+ else
360
+ File.open(first_file_name, 'ab') do |file|
361
+ file.write(File.read(second_file_name))
362
+ end
363
+ status_code = "SUCCESSFUL"
364
+ end
365
+ end
366
+
367
+ when "REPLACE_FILE"
368
+ if self.bucket
369
+ client = OpenC3::Bucket.getClient()
370
+ if not client.check_object(bucket: self.bucket, key: first_file_name)
371
+ status_code = "FILE_1_DOES_NOT_EXIST"
372
+ elsif not client.check_object(bucket: self.bucket, key: second_file_name)
373
+ status_code = "FILE_2_DOES_NOT_EXIST"
374
+ else
375
+ temp = Tempfile.new
376
+ client.get_object(bucket: self.bucket, key: second_file_name, path: temp.path)
377
+ client.put_object(bucket: self.bucket, key: first_file_name, body: temp.read)
378
+ temp.unlink
379
+ status_code = "SUCCESSFUL"
380
+ end
381
+ else
382
+ if not File.exist?(first_file_name)
383
+ status_code = "FILE_1_DOES_NOT_EXIST"
384
+ elsif not File.exist?(second_file_name)
385
+ status_code = "FILE_2_DOES_NOT_EXIST"
386
+ else
387
+ File.open(first_file_name, 'wb') do |file|
388
+ file.write(File.read(second_file_name))
389
+ end
390
+ status_code = "SUCCESSFUL"
391
+ end
392
+ end
393
+
394
+ when "CREATE_DIRECTORY"
395
+ # Creating a directory in a bucket doesn't make sense so it's a noop
396
+ FileUtils.mkdir(first_file_name) unless self.bucket
397
+ status_code = "SUCCESSFUL"
398
+
399
+ when "REMOVE_DIRECTORY"
400
+ if self.bucket
401
+ # Stand alone directories don't make sense in buckets because
402
+ # it's only files which are stored and the path is a string.
403
+ # Thus we'll just always return SUCCESSFUL.
404
+ status_code = "SUCCESSFUL"
405
+ else
406
+ if not Dir.exist?(first_file_name)
407
+ status_code = "DOES_NOT_EXIST"
408
+ else
409
+ FileUtils.rmdir(first_file_name)
410
+ status_code = "SUCCESSFUL"
411
+ end
412
+ end
413
+
414
+ when "DENY_FILE"
415
+ if self.bucket
416
+ begin
417
+ OpenC3::Bucket.getClient().delete_object(bucket: self.bucket, key: first_file_name)
418
+ rescue
419
+ # Don't care if the file doesn't exist
420
+ end
421
+ status_code = "SUCCESSFUL"
422
+ else
423
+ if File.exist?(first_file_name)
424
+ FileUtils.rm(first_file_name)
425
+ status_code = "SUCCESSFUL"
426
+ else
427
+ status_code = "SUCCESSFUL"
428
+ end
429
+ end
430
+
431
+ when "DENY_DIRECTORY"
432
+ if self.bucket
433
+ # Stand alone directories don't make sense in buckets because
434
+ # it's only files which are stored and the path is a string.
435
+ # Thus we'll just always return SUCCESSFUL.
436
+ status_code = "SUCCESSFUL"
437
+ else
438
+ if not Dir.exist?(first_file_name)
439
+ status_code = "SUCCESSFUL"
440
+ else
441
+ FileUtils.rmdir(first_file_name)
442
+ status_code = "SUCCESSFUL"
443
+ end
444
+ end
445
+
446
+ else
447
+ status_code = "NOT_PERFORMED"
448
+ filestore_message = "Unknown action code: #{action_code}"
449
+ end
450
+ rescue => err
451
+ if action_code != "CREATE_DIRECTORY"
452
+ status_code = "NOT_ALLOWED"
453
+ else
454
+ status_code = "CANNOT_BE_CREATED"
455
+ end
456
+ filestore_message = "#{err.class}:#{err.message}"
457
+ end
458
+
459
+ return status_code, filestore_message
460
+ end
461
+
462
+ def self.setup
463
+ # Get options for our microservice
464
+ model = OpenC3::MicroserviceModel.get_model(name: ENV['OPENC3_MICROSERVICE_NAME'], scope: ENV['OPENC3_SCOPE'])
465
+
466
+ # Initialize MIB from OPTIONS
467
+ current_entity_id = nil
468
+ source_entity_defined = false
469
+ destination_entity_defined = false
470
+ root_path_defined = false
471
+ model.options.each do |option|
472
+ field_name = option[0].to_s.downcase
473
+ value = option[1..-1]
474
+ value = value[0] if value.length == 1
475
+ case field_name
476
+ when 'source_entity_id'
477
+ source_entity_defined = true
478
+ current_entity_id = Integer(value)
479
+ CfdpMib.define_entity(current_entity_id)
480
+ CfdpMib.source_entity_id = current_entity_id
481
+ when 'destination_entity_id'
482
+ destination_entity_defined = true
483
+ current_entity_id = Integer(value)
484
+ CfdpMib.define_entity(current_entity_id)
485
+ when 'bucket'
486
+ CfdpMib.bucket = value
487
+ when 'root_path'
488
+ root_path_defined = true
489
+ CfdpMib.root_path = value
490
+ else
491
+ if current_entity_id
492
+ case field_name
493
+ when 'protocol_version_number', 'ack_timer_interval', 'nak_timer_interval', 'keep_alive_interval', 'check_interval', 'maximum_file_segment_length',
494
+ 'ack_timer_expiration_limit', 'nak_timer_expiration_limit', 'transaction_inactivity_limit', 'check_limit', 'keep_alive_discrepancy_limit'
495
+ CfdpMib.set_entity_value(current_entity_id, field_name, Integer(value))
496
+ when 'cmd_info', 'tlm_info'
497
+ if value.length == 3
498
+ CfdpMib.set_entity_value(current_entity_id, field_name, value)
499
+ else
500
+ raise "Value for MIB setting #{field_name} must be a three part array of target_name, packet_name, item_name"
501
+ end
502
+ when 'immediate_nak_mode', 'crcs_required', 'eof_sent_indication', 'eof_recv_indication', 'file_segment_recv_indication', 'transaction_finished_indication', 'suspended_indication', 'resume_indication',
503
+ 'enable_acks', 'enable_keep_alive', 'enable_finished', 'enable_eof_nak'
504
+ value = OpenC3::ConfigParser.handle_true_false(value)
505
+ if value == true or value == false
506
+ CfdpMib.set_entity_value(current_entity_id, field_name, value)
507
+ else
508
+ raise "Value for MIB setting #{field_name} must be true or false"
509
+ end
510
+ when 'default_transmission_mode'
511
+ value = value.to_s.upcase
512
+ if ['ACKNOWLEDGED', 'UNACKNOWLEDGED'].include?(value)
513
+ CfdpMib.set_entity_value(current_entity_id, field_name, value)
514
+ else
515
+ raise "Value for MIB setting #{field_name} must be ACKNOWLEDGED or UNACKNOWLEDGED"
516
+ end
517
+ when 'entity_id_length', 'sequence_number_length'
518
+ value = Integer(value)
519
+ if value >= 0 and value <= 7
520
+ CfdpMib.set_entity_value(current_entity_id, field_name, value)
521
+ else
522
+ raise "Value for MIB setting #{field_name} must be between 0 and 7"
523
+ end
524
+ when 'default_checksum_type'
525
+ value = Integer(value)
526
+ if value >= 0 and value <= 15
527
+ CfdpMib.set_entity_value(current_entity_id, field_name, value)
528
+ else
529
+ raise "Value for MIB setting #{field_name} must be between 0 and 15"
530
+ end
531
+ when 'transaction_closure_requested'
532
+ value = value.to_s.upcase
533
+ if ['CLOSURE_REQUESTED', 'CLOSURE_NOT_REQUESTED'].include?(value)
534
+ CfdpMib.set_entity_value(current_entity_id, field_name, value)
535
+ else
536
+ raise "Value for MIB setting #{field_name} must be CLOSURE_REQUESTED or CLOSURE_NOT_REQUESTED"
537
+ end
538
+ when 'incomplete_file_disposition'
539
+ value = value.to_s.upcaseD
540
+ if ['DISCARD', 'RETAIN'].include?(value)
541
+ CfdpMib.set_entity_value(current_entity_id, field_name, value)
542
+ else
543
+ raise "Value for MIB setting #{field_name} must be DISCARD or RETAIN"
544
+ end
545
+ when 'fault_handler'
546
+ fault_type = value[0].to_s.upcase
547
+ fault_response = value[1].to_s.upcase
548
+
549
+ raise "Value for MIB setting #{field_name} fault_type must be #{KNOWN_FAULT_TYPES.join(", ")}" unless KNOWN_FAULT_TYPES.include?(fault_type)
550
+ raise "Value for MIB setting #{field_name} fault_response must be #{KNOWN_FAULT_RESPONSES.join(", ")}" unless KNOWN_FAULT_RESPONSES.include?(fault_type)
551
+ entity = CfdpMib.entity(current_entity_id)
552
+ entity['fault_handler'][fault_type] = fault_response
553
+
554
+ else
555
+ raise "Unknown MIB setting #{field_name}"
556
+ end
557
+ else
558
+ raise "Must declare source_entity_id or destination_entity_id before other options"
559
+ end
560
+ end
561
+ end
562
+
563
+ raise "OPTION source_entity_id is required" unless source_entity_defined
564
+ raise "OPTION destination_entity_id is required" unless destination_entity_defined
565
+ raise "OPTION root_path is required" unless root_path_defined
566
+ end
567
+
568
+ def self.directory_listing(directory_name, directory_file_name)
569
+ # Apply root path
570
+ directory_name = File.join(@@root_path, directory_name.to_s)
571
+ directory_file_name = File.join(@@root_path, directory_file_name.to_s)
572
+
573
+ # Handle file path safety
574
+ directory_name = File.absolute_path(directory_name)
575
+ directory_file_name = File.absolute_path(directory_file_name)
576
+ if (directory_name.index(@@root_path) != 0) or (directory_file_name.index(@@root_path) != 0)
577
+ return nil
578
+ end
579
+
580
+ result = []
581
+ if self.bucket
582
+ dirs, files = OpenC3::Bucket.getClient().list_files(bucket: self.bucket, path: directory_name)
583
+ dirs.each do |dir|
584
+ result << {"directory" => dir}
585
+ end
586
+ files.each do |file|
587
+ result << file
588
+ end
589
+ else
590
+ entries = Dir.entries(directory_name)
591
+ entries.each do |entry|
592
+ next if entry == '.' or entry == '..'
593
+ full_name = File.join(directory_name, entry)
594
+ if File.directory?(full_name)
595
+ result << {"directory" => entry}
596
+ else
597
+ stat = File.stat(full_name)
598
+ result << {"name" => entry, "modified" => stat.mtime.to_s, "size" => stat.size}
599
+ end
600
+ end
601
+ end
602
+ json_result = JSON.pretty_generate(result.as_json)
603
+ return json_result
604
+ end
605
+
606
+ def self.clear
607
+ @@source_entity_id = 0
608
+ @@entities = {}
609
+ @@bucket = nil
610
+ @@root_path = "/"
611
+ @@transactions = {}
612
+ end
613
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2023 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # Licensed for Evaluation and Educational Use
7
+ #
8
+ # This file may only be used commercially under the terms of a commercial license
9
+ # purchased from OpenC3, Inc.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
+ #
15
+ # The development of this software was funded in-whole or in-part by MethaneSAT LLC.
16
+
17
+ require 'openc3/utilities/store'
18
+
19
+ class CfdpModel
20
+ def self.get_next_transaction_seq_num
21
+ key = "cfdp/#{ENV['OPENC3_MICROSERVICE_NAME']}/transaction_seq_num"
22
+ transaction_seq_num = OpenC3::Store.incr(key)
23
+ return transaction_seq_num
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2023 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # Licensed for Evaluation and Educational Use
7
+ #
8
+ # This file may only be used commercially under the terms of a commercial license
9
+ # purchased from OpenC3, Inc.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
+ #
15
+ # The development of this software was funded in-whole or in-part by MethaneSAT LLC.
16
+
17
+ class CfdpNullChecksum
18
+ def add(offset, data)
19
+ return 0
20
+ end
21
+
22
+ def checksum(file, full_checksum_needed)
23
+ return 0
24
+ end
25
+
26
+ def check(file, other_checkum, full_checksum_needed)
27
+ true
28
+ end
29
+ end