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,1965 @@
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 'rails_helper'
18
+ require 'openc3/script'
19
+ require 'openc3/api/api'
20
+ require 'openc3/models/microservice_model'
21
+ require 'openc3/utilities/store_autoload'
22
+ require 'openc3/topics/command_topic'
23
+
24
+ OpenC3.disable_warnings do
25
+ # Load the json_rpc.rb to ensure it overrides anything Rails is doing with as_json
26
+ load 'openc3/io/json_rpc.rb'
27
+ end
28
+
29
+ module OpenC3
30
+ RSpec.describe "cfdp", type: :request do
31
+ describe "POST /cfdp/put" do
32
+ before(:each) do
33
+ mock_redis()
34
+ CfdpMib.clear
35
+ @root_path = SPEC_DIR
36
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt') if File.exist?(File.join(SPEC_DIR, 'test1.txt'))
37
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt') if File.exist?(File.join(SPEC_DIR, 'test2.txt'))
38
+ FileUtils.rm File.join(SPEC_DIR, 'new_file.txt') if File.exist?(File.join(SPEC_DIR, 'new_file.txt'))
39
+ FileUtils.rm File.join(SPEC_DIR, 'rename_file.txt') if File.exist?(File.join(SPEC_DIR, 'rename_file.txt'))
40
+ FileUtils.rm File.join(SPEC_DIR, 'deny.txt') if File.exist?(File.join(SPEC_DIR, 'deny.txt'))
41
+ FileUtils.rm File.join(SPEC_DIR, 'first.txt') if File.exist?(File.join(SPEC_DIR, 'first.txt'))
42
+ FileUtils.rm File.join(SPEC_DIR, 'second.txt') if File.exist?(File.join(SPEC_DIR, 'second.txt'))
43
+ FileUtils.rm_rf(File.join(SPEC_DIR, 'new_dir')) if File.exist?(File.join(SPEC_DIR, 'new_dir'))
44
+ FileUtils.rm_rf(File.join(SPEC_DIR, 'deny_dir')) if File.exist?(File.join(SPEC_DIR, 'deny_dir'))
45
+ FileUtils.rm_rf(File.join(SPEC_DIR, 'another_dir')) if File.exist?(File.join(SPEC_DIR, 'another_dir'))
46
+ end
47
+
48
+ def safe_write_topic(*args)
49
+ begin
50
+ Topic.write_topic(*args)
51
+ rescue RuntimeError
52
+ # Work around mock_redis hash issue
53
+ sleep(0.1)
54
+ retry
55
+ end
56
+ end
57
+
58
+ def setup(source_id:, destination_id:)
59
+ @source_entity_id = source_id
60
+ @destination_entity_id = destination_id
61
+ ENV['OPENC3_MICROSERVICE_NAME'] = 'DEFAULT__API__CFDP'
62
+ # Create the model that is consumed by CfdpMib.setup
63
+ model = MicroserviceModel.new(name: ENV['OPENC3_MICROSERVICE_NAME'], scope: "DEFAULT",
64
+ options: [
65
+ ["source_entity_id", @source_entity_id],
66
+ ["cmd_info", "CFDPTEST", "CFDP_PDU", "PDU"],
67
+ ["tlm_info", "CFDPTEST", "CFDP_PDU", "PDU"],
68
+ ["destination_entity_id", @destination_entity_id],
69
+ ["cmd_info", "CFDPTEST", "CFDP_PDU", "PDU"],
70
+ ["tlm_info", "CFDPTEST", "CFDP_PDU", "PDU"],
71
+ ["root_path", SPEC_DIR],
72
+ ],
73
+ )
74
+ model.create
75
+ CfdpMib.setup
76
+
77
+ @tx_pdus = []
78
+ @rx_pdus = []
79
+ @source_packets = []
80
+ @receive_packets = []
81
+ @source_packet_mutex = Mutex.new
82
+ @receive_packet_mutex = Mutex.new
83
+ allow_any_instance_of(CfdpSourceTransaction).to receive('cmd') do |source, tgt_name, pkt_name, params|
84
+ # puts params["PDU"].formatted # Prints the raw bytes
85
+ @source_packet_mutex.synchronize do
86
+ @source_packets << [tgt_name, pkt_name, params]
87
+ begin
88
+ @tx_pdus << CfdpPdu.decom(params["PDU"])
89
+ rescue
90
+ end
91
+ end
92
+ end
93
+ allow_any_instance_of(CfdpReceiveTransaction).to receive('cmd') do |source, tgt_name, pkt_name, params|
94
+ @receive_packet_mutex.synchronize do
95
+ @receive_packets << [tgt_name, pkt_name, params]
96
+ begin
97
+ @rx_pdus << CfdpPdu.decom(params["PDU"])
98
+ rescue
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ # Helper method to perform a filestore_request and return the indication
105
+ # This does all the work and creates all the fault conditions using keyword args
106
+ def request(source: nil, dest: nil, requests: [], overrides: [], messages: [], flow_label: nil,
107
+ transmission_mode: 'UNACKNOWLEDGED', closure: 'CLOSURE_NOT_REQUESTED',
108
+ send_closure: true, cancel: false, skip: false, duplicate_metadata: false,
109
+ duplicate_filedata: false, bad_seg_size: false, eof_size: nil,
110
+ prompt: nil, crcs_required: nil, cycles: 1)
111
+ setup(source_id: 1, destination_id: 2) unless CfdpMib.entity(@destination_entity_id)
112
+ CfdpMib.set_entity_value(@destination_entity_id, 'maximum_file_segment_length', 8)
113
+ CfdpMib.root_path = @root_path
114
+ if @bucket
115
+ CfdpMib.bucket = @bucket
116
+ else
117
+ CfdpMib.bucket = nil
118
+ end
119
+
120
+ post "/cfdp/put", :params => {
121
+ scope: "DEFAULT", destination_entity_id: @destination_entity_id,
122
+ source_file_name: source, destination_file_name: dest,
123
+ filestore_requests: requests, closure_requested: closure,
124
+ transmission_mode: transmission_mode,
125
+ fault_handler_overrides: overrides,
126
+ messages_to_user: messages,
127
+ flow_label: flow_label
128
+ }, as: :json
129
+ expect(response).to have_http_status(200)
130
+ sleep 0.1
131
+
132
+ # Start user thread here so it has the chance to receive the closure PDU
133
+ @user = CfdpUser.new
134
+ @user.start
135
+ sleep 0.5 # Allow user thread to start
136
+
137
+ rx_transactions = {}
138
+ tx_transactions = {}
139
+ source_packet_index = 0
140
+ receive_packet_index = 0
141
+ cycles.times do
142
+ # Clear the tx transactions to simulate the receive side on the same system
143
+ tx_transactions = CfdpMib.transactions.clone
144
+ keys = CfdpMib.transactions.keys
145
+ keys.each do |key|
146
+ CfdpMib.transactions.delete(key)
147
+ end
148
+
149
+ # Restore the rx transactions and send the receive_packets
150
+ rx_transactions.each do |key, value|
151
+ # puts "restore:#{key} val:#{value}"
152
+ CfdpMib.transactions[key] = value
153
+ end
154
+
155
+ CfdpMib.source_entity_id = @destination_entity_id
156
+ CfdpMib.set_entity_value(@destination_entity_id, 'crcs_required', true) if crcs_required
157
+
158
+ i = -1
159
+ @source_packet_mutex.synchronize do
160
+ @source_packets[source_packet_index..-1].each do |target_name, cmd_name, cmd_params|
161
+ i += 1
162
+ # Skip is an array of segments to skip
163
+ # 1 based since segments start at packet 1, metadata is 0
164
+ next if skip and skip.include?(i)
165
+
166
+ if eof_size and i == 2
167
+ # See the cfdp_pdu_eof_spec.rb for the structure
168
+ cmd_params['PDU'][16] = [eof_size].pack('C') #"\x07" # Hack to be less than 8
169
+ end
170
+ if bad_seg_size and i == 2
171
+ # See the cfdp_pdu_file_data_spec.rb for the structure
172
+ cmd_params['PDU'][10] = "\x0A" # Hack to be more than 8
173
+ end
174
+
175
+ if prompt
176
+ # Need to sneak the prompt before the EOF PDU which is last
177
+ if i == @source_packets.length - 1
178
+ # Simlulate the prompt PDU
179
+ prompt_params = {}
180
+ prompt_params['PDU'] = CfdpPdu.build_prompt_pdu(
181
+ source_entity: CfdpMib.entity(@source_entity_id),
182
+ transaction_seq_num: 1,
183
+ destination_entity: CfdpMib.entity(@destination_entity_id),
184
+ transmission_mode: transmission_mode,
185
+ response_required: prompt
186
+ )
187
+ msg_hash = {
188
+ :time => Time.now.to_nsec_from_epoch,
189
+ :stored => 'false',
190
+ :target_name => "CFDPTEST",
191
+ :packet_name => "CFDP_PDU",
192
+ :received_count => 1,
193
+ :json_data => JSON.generate(prompt_params.as_json(:allow_nan => true)),
194
+ }
195
+ safe_write_topic("DEFAULT__DECOM__{CFDPTEST}__CFDP_PDU", msg_hash, nil)
196
+ end
197
+ end
198
+
199
+ msg_hash = {
200
+ :time => Time.now.to_nsec_from_epoch,
201
+ :stored => 'false',
202
+ :target_name => target_name,
203
+ :packet_name => cmd_name,
204
+ :received_count => 1,
205
+ :json_data => JSON.generate(cmd_params.as_json(:allow_nan => true)),
206
+ }
207
+ safe_write_topic("DEFAULT__DECOM__{#{target_name}}__#{cmd_name}", msg_hash, nil)
208
+ # Duplicate metadata should be ignored
209
+ if i == 0 and duplicate_metadata
210
+ safe_write_topic("DEFAULT__DECOM__{#{target_name}}__#{cmd_name}", msg_hash, nil)
211
+ end
212
+ if i == 1
213
+ if duplicate_filedata
214
+ safe_write_topic("DEFAULT__DECOM__{#{target_name}}__#{cmd_name}", msg_hash, nil)
215
+ end
216
+ if cancel
217
+ post "/cfdp/cancel", :params => {
218
+ scope: "DEFAULT", transaction_id: "#{@source_entity_id}__1"
219
+ }, as: :json
220
+ sleep 0.5
221
+ end
222
+ end
223
+ end
224
+ source_packet_index = @source_packets.length
225
+ end
226
+ sleep 0.5 # Allow them to be processed
227
+
228
+ # Clear the rx transactions to simulate the transmit side on the same system
229
+ rx_transactions = CfdpMib.transactions.clone
230
+ keys = CfdpMib.transactions.keys
231
+ keys.each do |key|
232
+ CfdpMib.transactions.delete(key)
233
+ end
234
+
235
+ # Restore the tx transactions and send the receive_packets
236
+ tx_transactions.each do |key, value|
237
+ # puts "restore:#{key} val:#{value}"
238
+ CfdpMib.transactions[key] = value
239
+ end
240
+ CfdpMib.source_entity_id = @source_entity_id
241
+
242
+ @receive_packet_mutex.synchronize do
243
+ @receive_packets[receive_packet_index..-1].each do |target_name, cmd_name, cmd_params|
244
+ if send_closure
245
+ msg_hash = {
246
+ :time => Time.now.to_nsec_from_epoch,
247
+ :stored => 'false',
248
+ :target_name => target_name,
249
+ :packet_name => cmd_name,
250
+ :received_count => 1,
251
+ :json_data => JSON.generate(cmd_params.as_json(:allow_nan => true)),
252
+ }
253
+ safe_write_topic("DEFAULT__DECOM__{#{target_name}}__#{cmd_name}", msg_hash, nil)
254
+ end
255
+ end
256
+ receive_packet_index = @receive_packets.length
257
+ end
258
+
259
+ sleep 0.5
260
+ end
261
+
262
+ @user.stop
263
+ sleep 0.1
264
+
265
+ get "/cfdp/indications", :params => { scope: "DEFAULT" }
266
+ expect(response).to have_http_status(200)
267
+ json = JSON.parse(response.body)
268
+ yield json['indications'] if block_given?
269
+ end
270
+
271
+ it "requires a destination_entity_id" do
272
+ setup(source_id: 10, destination_id: 20)
273
+ post "/cfdp/put", :params => { scope: "DEFAULT" }
274
+ expect(response).to have_http_status(400)
275
+ expect(response.body).to match(/missing.*destination_entity_id/)
276
+ end
277
+
278
+ it "requires a numeric destination_entity_id" do
279
+ setup(source_id: 10, destination_id: 20)
280
+ post "/cfdp/put", :params => { scope: "DEFAULT", destination_entity_id: "HI" }
281
+ expect(response).to have_http_status(400)
282
+ end
283
+
284
+ # Section 4.1 CRC Procedures
285
+ [true, false].each do |required|
286
+ context "with CRCs required #{required}" do
287
+ it "sends a file" do
288
+ setup(source_id: 1, destination_id: 2)
289
+ CfdpMib.set_entity_value(@source_entity_id, 'crcs_required', required)
290
+ CfdpMib.set_entity_value(@destination_entity_id, 'crcs_required', required)
291
+
292
+ data = ('a'..'z').to_a.shuffle[0,8].join
293
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
294
+ request(source: 'test1.txt', dest: 'test2.txt') do |indications|
295
+ # First the transmit indications
296
+ expect(indications[0]['indication_type']).to eql 'Transaction'
297
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
298
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
299
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
300
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
301
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
302
+ expect(indications[2]['status_report']).to eql 'FINISHED'
303
+ # Receive indications
304
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
305
+ expect(indications[3]['source_file_name']).to eql 'test1.txt'
306
+ expect(indications[3]['destination_file_name']).to eql 'test2.txt'
307
+ expect(indications[3]['file_size']).to eql 8
308
+ expect(indications[3]['source_entity_id']).to eql 1
309
+ expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
310
+ expect(indications[4]['offset']).to eql 0
311
+ expect(indications[4]['length']).to eql 8
312
+ expect(indications[5]['indication_type']).to eql 'EOF-Recv'
313
+ expect(indications[6]['indication_type']).to eql 'Transaction-Finished'
314
+ expect(indications[6]['condition_code']).to eql 'NO_ERROR'
315
+ expect(indications[6]['file_status']).to eql 'FILESTORE_SUCCESS'
316
+ expect(indications[6]['delivery_code']).to eql 'DATA_COMPLETE'
317
+ expect(indications[6]['status_report']).to eql 'FINISHED'
318
+ end
319
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
320
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
321
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
322
+
323
+ expect(CfdpMib.entity(@destination_entity_id)['crcs_required']).to be required
324
+ # REQ 4.1.3.1, 4.1.3.2
325
+ # Check the CRC algorithm and position
326
+ @source_packets.each do |packet|
327
+ pdu_data = packet[2]["PDU"]
328
+ calculated = OpenC3::Crc16.new.calc(pdu_data[0..-3])
329
+ if required
330
+ expect(calculated).to eql pdu_data[-2..-1].unpack("n")[0]
331
+ else
332
+ expect(calculated).not_to eql pdu_data[-2..-1].unpack("n")[0]
333
+ end
334
+ end
335
+
336
+ # Validate the PDUs
337
+ expect(@tx_pdus.length).to eql 3
338
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
339
+ expect(@tx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
340
+ expect(@tx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
341
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
342
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
343
+
344
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
345
+ expect(@tx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
346
+ expect(@tx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
347
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data
348
+
349
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
350
+ expect(@tx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
351
+ expect(@tx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
352
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
353
+ expect(@tx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
354
+ end
355
+ end
356
+ end
357
+
358
+ it "raises if CRCs are required and do not exist" do
359
+ setup(source_id: 1, destination_id: 2)
360
+ CfdpMib.set_entity_value(@source_entity_id, 'crcs_required', true)
361
+ CfdpMib.set_entity_value(@destination_entity_id, 'crcs_required', false)
362
+ # We get three exceptions from the 3 PDUs that we process
363
+ expect(Logger).to receive(:error).exactly(3).times.with(/PDU without required CRC received/, scope: 'DEFAULT')
364
+ data = ('a'..'z').to_a.shuffle[0,8].join
365
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
366
+ request(source: 'test1.txt', dest: 'test2.txt', crcs_required: true)
367
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be false
368
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
369
+ end
370
+ # END Section 4.1 CRC Procedures
371
+
372
+ # Section 4.2 Checksum Procedures
373
+ it "uses a null checksum (zeros)" do
374
+ setup(source_id: 1, destination_id: 2)
375
+ CfdpMib.set_entity_value(@source_entity_id, 'default_checksum_type', 15)
376
+ CfdpMib.set_entity_value(@destination_entity_id, 'default_checksum_type', 15)
377
+ data = ('a'..'z').to_a.shuffle[0,8].join
378
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
379
+ request(source: 'test1.txt', dest: 'test2.txt') do |indications|
380
+ # First the transmit indications
381
+ expect(indications[0]['indication_type']).to eql 'Transaction'
382
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
383
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
384
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
385
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
386
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
387
+ expect(indications[2]['status_report']).to eql 'FINISHED'
388
+ # Receive indications
389
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
390
+ expect(indications[3]['source_file_name']).to eql 'test1.txt'
391
+ expect(indications[3]['destination_file_name']).to eql 'test2.txt'
392
+ expect(indications[3]['file_size']).to eql 8
393
+ expect(indications[3]['source_entity_id']).to eql 1
394
+ expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
395
+ expect(indications[4]['offset']).to eql 0
396
+ expect(indications[4]['length']).to eql 8
397
+ expect(indications[5]['indication_type']).to eql 'EOF-Recv'
398
+ expect(indications[6]['indication_type']).to eql 'Transaction-Finished'
399
+ expect(indications[6]['condition_code']).to eql 'NO_ERROR'
400
+ expect(indications[6]['file_status']).to eql 'FILESTORE_SUCCESS'
401
+ expect(indications[6]['delivery_code']).to eql 'DATA_COMPLETE'
402
+ expect(indications[6]['status_report']).to eql 'FINISHED'
403
+ end
404
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
405
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
406
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
407
+
408
+ # Validate the PDUs
409
+ expect(@tx_pdus.length).to eql 3
410
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
411
+ expect(@tx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
412
+ expect(@tx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
413
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
414
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
415
+
416
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
417
+ expect(@tx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
418
+ expect(@tx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
419
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data
420
+
421
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
422
+ expect(@tx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
423
+ expect(@tx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
424
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
425
+ expect(@tx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
426
+ # NULL Checksum is 0
427
+ expect(@tx_pdus[2]['FILE_CHECKSUM']).to eql 0
428
+ end
429
+
430
+ it "handles a failed checksum" do
431
+ data = ('a'..'z').to_a.shuffle[0,8].join
432
+ expect_any_instance_of(CfdpChecksum).to receive(:check).and_return(false)
433
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
434
+ request(source: 'test1.txt', dest: 'test2.txt') do |indications|
435
+ # First the transmit indications
436
+ expect(indications[0]['indication_type']).to eql 'Transaction'
437
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
438
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
439
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
440
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
441
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
442
+ expect(indications[2]['status_report']).to eql 'FINISHED'
443
+ # Receive indications
444
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
445
+ expect(indications[3]['source_file_name']).to eql 'test1.txt'
446
+ expect(indications[3]['destination_file_name']).to eql 'test2.txt'
447
+ expect(indications[3]['file_size']).to eql 8
448
+ expect(indications[3]['source_entity_id']).to eql 1
449
+ expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
450
+ expect(indications[4]['offset']).to eql 0
451
+ expect(indications[4]['length']).to eql 8
452
+ expect(indications[5]['indication_type']).to eql 'EOF-Recv'
453
+ # 4.6.1.2.8 d. File Checksum Failure fault
454
+ expect(indications[6]['indication_type']).to eql 'Fault'
455
+ expect(indications[6]['condition_code']).to eql 'FILE_CHECKSUM_FAILURE'
456
+ expect(indications[6]['progress']).to eql 8
457
+ expect(indications[7]['indication_type']).to eql 'Transaction-Finished'
458
+ expect(indications[7]['condition_code']).to eql 'FILE_CHECKSUM_FAILURE'
459
+ expect(indications[7]['file_status']).to eql 'FILE_DISCARDED'
460
+ expect(indications[7]['delivery_code']).to eql 'DATA_INCOMPLETE'
461
+ expect(indications[7]['status_report']).to eql 'FINISHED'
462
+ end
463
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be false
464
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
465
+
466
+ # Validate the PDUs
467
+ expect(@tx_pdus.length).to eql 3
468
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
469
+ expect(@tx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
470
+ expect(@tx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
471
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
472
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
473
+
474
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
475
+ expect(@tx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
476
+ expect(@tx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
477
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data
478
+
479
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
480
+ expect(@tx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
481
+ expect(@tx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
482
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
483
+ expect(@tx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
484
+ expect(@tx_pdus[2]['FILE_CHECKSUM']).not_to eql 0
485
+ end
486
+ # END Section 4.2 Checksum Procedures
487
+
488
+ # Section 4.3 Put Procedures
489
+ it "requests metadata only with no source and dest" do
490
+ request() do |indications|
491
+ expect(indications[0]['indication_type']).to eql 'Transaction'
492
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
493
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
494
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
495
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
496
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
497
+ expect(indications[2]['status_report']).to eql 'FINISHED'
498
+ # Receive indications
499
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
500
+ expect(indications[3]['source_file_name']).to be nil
501
+ expect(indications[3]['destination_file_name']).to be nil
502
+ expect(indications[3]['file_size']).to eql 0
503
+ expect(indications[3]['source_entity_id']).to eql 1
504
+ expect(indications[4]['indication_type']).to eql 'EOF-Recv'
505
+ expect(indications[5]['indication_type']).to eql 'Transaction-Finished'
506
+ expect(indications[5]['condition_code']).to eql 'NO_ERROR'
507
+ expect(indications[5]['file_status']).to eql 'UNREPORTED'
508
+ expect(indications[5]['delivery_code']).to eql 'DATA_COMPLETE'
509
+ expect(indications[5]['status_report']).to eql 'FINISHED'
510
+ end
511
+ # Validate the PDUs
512
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
513
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
514
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DIRECTIVE'
515
+ expect(@tx_pdus[1]['DIRECTIVE_CODE']).to eql 'EOF'
516
+ expect(@tx_pdus[2]).to be nil
517
+ end
518
+ # END Section 4.3 Put Procedures
519
+
520
+ # Section 4.4 Transaction Start Notification Procedure
521
+ it "creates unique transactions IDs" do
522
+ data = ('a'..'z').to_a.shuffle[0,8].join
523
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
524
+ request(source: 'test1.txt', dest: 'test2.txt')
525
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
526
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
527
+ request(source: 'test1.txt', dest: 'test3.txt') do |indications|
528
+ # The existing notifications are still there
529
+ expect(indications[0]['indication_type']).to eql 'Transaction'
530
+ expect(indications[0]['transaction_id']).to eql '1__1'
531
+ # New transaction has new ID
532
+ expect(indications[7]['indication_type']).to eql 'Transaction'
533
+ expect(indications[7]['transaction_id']).to eql '1__2'
534
+ end
535
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
536
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
537
+ FileUtils.rm File.join(SPEC_DIR, 'test3.txt')
538
+
539
+ # Validate the PDUs
540
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
541
+ expect(@tx_pdus[0]['SEQUENCE_NUMBER']).to eql 1
542
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
543
+ expect(@tx_pdus[1]['SEQUENCE_NUMBER']).to eql 1
544
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
545
+ expect(@tx_pdus[2]['SEQUENCE_NUMBER']).to eql 1
546
+
547
+ expect(@tx_pdus[3]['TYPE']).to eql 'FILE_DIRECTIVE'
548
+ expect(@tx_pdus[3]['SEQUENCE_NUMBER']).to eql 2
549
+ expect(@tx_pdus[4]['TYPE']).to eql 'FILE_DATA'
550
+ expect(@tx_pdus[4]['SEQUENCE_NUMBER']).to eql 2
551
+ expect(@tx_pdus[5]['TYPE']).to eql 'FILE_DIRECTIVE'
552
+ expect(@tx_pdus[5]['SEQUENCE_NUMBER']).to eql 2
553
+ end
554
+ # END Section 4.4 Transaction Start Notification Procedure
555
+
556
+ # No tests for Section 4.5 PDU FORWARDING PROCEDURES
557
+
558
+ # Section 4.6 COPY FILE PROCEDURES
559
+
560
+ # 4.6.1.1.1 Sending Entity Unacknowleged
561
+ # 4.6.1.2.1 Receving Entity Unacknowleged
562
+ it "performs unacknowldged transfers" do
563
+ data = ('a'..'z').to_a.shuffle[0,8].join
564
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
565
+ request(source: 'test1.txt', dest: 'test2.txt',
566
+ transmission_mode: 'UNACKNOWLEDGED',
567
+ messages: [
568
+ 'This is a test',
569
+ 'Another message'
570
+ ],
571
+ overrides: [
572
+ ['ACK_LIMIT_REACHED', 'ISSUE_NOTICE_OF_CANCELLATION'],
573
+ ['FILE_CHECKSUM_FAILURE', 'IGNORE_ERROR']
574
+ ],
575
+ requests: [
576
+ ['CREATE_FILE', "temp.txt"],
577
+ ['DELETE_FILE', "temp.txt"],
578
+ ],
579
+ flow_label: 'FLOW') do |indications|
580
+ # First the transmit indications
581
+ expect(indications[0]['indication_type']).to eql 'Transaction'
582
+ # 4.6.1.1.9 b. EOF-Sent indication
583
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
584
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
585
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
586
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
587
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
588
+ expect(indications[2]['status_report']).to eql 'FINISHED'
589
+ # Receive indications
590
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
591
+ expect(indications[3]['source_file_name']).to eql 'test1.txt'
592
+ expect(indications[3]['destination_file_name']).to eql 'test2.txt'
593
+ expect(indications[3]['file_size']).to eql 8
594
+ expect(indications[3]['source_entity_id']).to eql 1
595
+ expect(indications[3]['fault_handler_overrides']).to eql({"ACK_LIMIT_REACHED"=>"ISSUE_NOTICE_OF_CANCELLATION", "FILE_CHECKSUM_FAILURE"=>"IGNORE_ERROR"})
596
+ # 4.6.1.2.6 Metadata-Recv includes messags to user
597
+ expect(indications[3]['messages_to_user']).to eql ["This is a test", "Another message"]
598
+ expect(indications[3]['filestore_requests']).to eql [{"ACTION_CODE"=>"CREATE_FILE", "FIRST_FILE_NAME"=>"temp.txt"}, {"ACTION_CODE"=>"DELETE_FILE", "FIRST_FILE_NAME"=>"temp.txt"}]
599
+ expect(indications[3]['flow_label']).to eql "FLOW"
600
+ expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
601
+ expect(indications[4]['offset']).to eql 0
602
+ expect(indications[4]['length']).to eql 8
603
+ expect(indications[5]['indication_type']).to eql 'EOF-Recv'
604
+ expect(indications[6]['indication_type']).to eql 'Transaction-Finished'
605
+ expect(indications[6]['condition_code']).to eql 'NO_ERROR'
606
+ expect(indications[6]['file_status']).to eql 'FILESTORE_SUCCESS'
607
+ expect(indications[6]['delivery_code']).to eql 'DATA_COMPLETE'
608
+ expect(indications[6]['status_report']).to eql 'FINISHED'
609
+ end
610
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
611
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
612
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
613
+
614
+ # 4.6.1.1.2 Metadata PDU
615
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
616
+ # 4.6.1.1.3 a. Metadata PDU contents size of the file
617
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
618
+ # 4.6.1.1.3 b. Metadata PDU contents source name
619
+ expect(@tx_pdus[0]['SOURCE_FILE_NAME']).to eql 'test1.txt'
620
+ # 4.6.1.1.3 b. Metadata PDU contents destination name
621
+ expect(@tx_pdus[0]['DESTINATION_FILE_NAME']).to eql 'test2.txt'
622
+ # 4.6.1.1.3 c. Metadata PDU contents Fault handler overrides
623
+ expect(@tx_pdus[0]['TLVS'][0]['TYPE']).to eql 'FAULT_HANDLER_OVERRIDE'
624
+ expect(@tx_pdus[0]['TLVS'][0]['CONDITION_CODE']).to eql 'ACK_LIMIT_REACHED'
625
+ expect(@tx_pdus[0]['TLVS'][0]['HANDLER_CODE']).to eql 'ISSUE_NOTICE_OF_CANCELLATION'
626
+ expect(@tx_pdus[0]['TLVS'][1]['TYPE']).to eql 'FAULT_HANDLER_OVERRIDE'
627
+ expect(@tx_pdus[0]['TLVS'][1]['CONDITION_CODE']).to eql 'FILE_CHECKSUM_FAILURE'
628
+ expect(@tx_pdus[0]['TLVS'][1]['HANDLER_CODE']).to eql 'IGNORE_ERROR'
629
+ # 4.6.1.1.3 c. Metadata PDU contents Messages to User
630
+ expect(@tx_pdus[0]['TLVS'][2]['TYPE']).to eql 'MESSAGE_TO_USER'
631
+ expect(@tx_pdus[0]['TLVS'][2]['MESSAGE_TO_USER']).to eql 'This is a test'
632
+ expect(@tx_pdus[0]['TLVS'][3]['TYPE']).to eql 'MESSAGE_TO_USER'
633
+ expect(@tx_pdus[0]['TLVS'][3]['MESSAGE_TO_USER']).to eql 'Another message'
634
+ # 4.6.1.1.3 c. Metadata PDU contents Filestore requests
635
+ expect(@tx_pdus[0]['TLVS'][4]['TYPE']).to eql 'FILESTORE_REQUEST'
636
+ expect(@tx_pdus[0]['TLVS'][4]['ACTION_CODE']).to eql 'CREATE_FILE'
637
+ expect(@tx_pdus[0]['TLVS'][5]['TYPE']).to eql 'FILESTORE_REQUEST'
638
+ expect(@tx_pdus[0]['TLVS'][5]['ACTION_CODE']).to eql 'DELETE_FILE'
639
+ # 4.6.1.1.3 c. Metadata PDU contents Flow label
640
+ expect(@tx_pdus[0]['TLVS'][6]['TYPE']).to eql 'FLOW_LABEL'
641
+ expect(@tx_pdus[0]['TLVS'][6]['FLOW_LABEL']).to eql 'FLOW'
642
+ # 4.6.1.1.3 d. Metadata PDU contents
643
+ expect(@tx_pdus[0]['CLOSURE_REQUESTED']).to eql 'CLOSURE_NOT_REQUESTED'
644
+ # 4.6.1.1.3 e. Metadata PDU contents
645
+ expect(@tx_pdus[0]['CHECKSUM_TYPE']).to eql 0
646
+ # 4.6.1.1.1 Unacknowledged
647
+ expect(@tx_pdus[0]['TRANSMISSION_MODE']).to eql 'UNACKNOWLEDGED'
648
+
649
+ # 4.6.1.1.4 File Data PDUs
650
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
651
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data
652
+ # 4.6.1.1.5 Offset of segment
653
+ expect(@tx_pdus[1]['OFFSET']).to eql 0
654
+ # 4.6.1.1.5.1 Segment Metadata
655
+ expect(@tx_pdus[1]['SEGMENT_METADATA_FLAG']).to eql 'NOT_PRESENT'
656
+ # 4.6.1.1.5.[2,3,4] All relate to segment metadata ... not implemented
657
+ # 4.6.1.1.6, 4.6.1.1.7 Segmentation control
658
+ expect(@tx_pdus[1]['SEGMENTATION_CONTROL']).to eql 'NOT_PRESERVED'
659
+ # 4.6.1.1.8 Segmentation control invalid ... not implemented
660
+
661
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
662
+ # 4.6.1.1.9 a. EOF PDU
663
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
664
+ # 4.6.1.1.9 c. EOF PDU checksum & length
665
+ expect(@tx_pdus[2]['FILE_CHECKSUM']).not_to be nil
666
+ expect(@tx_pdus[2]['FILE_SIZE']).to eql 8
667
+ # 4.6.1.1.10 Flow label is implementation specific ... not implemented
668
+ end
669
+
670
+ # 4.6.1.1.1 Sending Entity Acknowleged
671
+ # 4.6.1.2.1 Receving Entity Acknowleged
672
+ it "sends data acknowledged" do
673
+ data = ('a'..'z').to_a.shuffle[0,8].join
674
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
675
+ request(source: 'test1.txt', dest: 'test2.txt',
676
+ transmission_mode: 'ACKNOWLEDGED',
677
+ # 4.6.1.2.4 Duplicate metadata discarded
678
+ duplicate_metadata: true) do |indications|
679
+ # First the transmit indications
680
+ expect(indications[0]['indication_type']).to eql 'Transaction'
681
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
682
+ # Receive indications
683
+ # 4.6.1.2.6 Metadata-Recv indication
684
+ expect(indications[2]['indication_type']).to eql 'Metadata-Recv'
685
+ expect(indications[2]['source_file_name']).to eql 'test1.txt'
686
+ expect(indications[2]['destination_file_name']).to eql 'test2.txt'
687
+ expect(indications[2]['file_size']).to eql 8
688
+ expect(indications[2]['source_entity_id']).to eql 1
689
+ expect(indications[3]['indication_type']).to eql 'File-Segment-Recv'
690
+ expect(indications[3]['offset']).to eql 0
691
+ expect(indications[3]['length']).to eql 8
692
+ expect(indications[4]['indication_type']).to eql 'EOF-Recv'
693
+ expect(indications[5]['indication_type']).to eql 'Transaction-Finished'
694
+ expect(indications[5]['condition_code']).to eql 'NO_ERROR'
695
+ expect(indications[5]['file_status']).to eql 'FILESTORE_SUCCESS'
696
+ expect(indications[5]['delivery_code']).to eql 'DATA_COMPLETE'
697
+ expect(indications[5]['status_report']).to eql 'FINISHED'
698
+
699
+ # Final transmit
700
+ # 4.6.3.2.1 Transmission of EOF causes Notice of Completion
701
+ expect(indications[6]['indication_type']).to eql 'Transaction-Finished'
702
+ expect(indications[6]['condition_code']).to eql 'NO_ERROR'
703
+ expect(indications[6]['file_status']).to eql 'FILESTORE_SUCCESS'
704
+ expect(indications[6]['delivery_code']).to eql 'DATA_COMPLETE'
705
+ expect(indications[6]['status_report']).to eql 'FINISHED'
706
+ end
707
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
708
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
709
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
710
+
711
+ # Validate the Tx PDUs
712
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
713
+ expect(@tx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
714
+ expect(@tx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
715
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
716
+ # 4.6.1.1.1 Acknowledged
717
+ expect(@tx_pdus[0]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
718
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
719
+
720
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
721
+ expect(@tx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
722
+ expect(@tx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
723
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data
724
+ expect(@tx_pdus[1]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
725
+
726
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
727
+ expect(@tx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
728
+ expect(@tx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
729
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
730
+ expect(@tx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
731
+ expect(@tx_pdus[2]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
732
+
733
+ expect(@tx_pdus[3]['TYPE']).to eql 'FILE_DIRECTIVE'
734
+ expect(@tx_pdus[3]['SOURCE_ENTITY_ID']).to eql @source_entity_id
735
+ expect(@tx_pdus[3]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
736
+ # 4.6.4.2.4 ACK Finished
737
+ expect(@tx_pdus[3]['DIRECTIVE_CODE']).to eql 'ACK'
738
+ expect(@tx_pdus[3]['ACK_DIRECTIVE_CODE']).to eql 'FINISHED'
739
+ expect(@tx_pdus[3]['ACK_DIRECTIVE_SUBTYPE']).to eql 1
740
+ expect(@tx_pdus[3]['CONDITION_CODE']).to eql 'NO_ERROR'
741
+ expect(@tx_pdus[3]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
742
+ expect(@tx_pdus[3]['TRANSACTION_STATUS']).to eql 'ACTIVE' # TODO: ACTIVE?
743
+
744
+ # Validate the Rx PDUs
745
+ expect(@rx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
746
+ expect(@rx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
747
+ expect(@rx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
748
+ expect(@rx_pdus[0]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
749
+ # 4.6.4.3.5 ACK the EOF
750
+ expect(@rx_pdus[0]['DIRECTIVE_CODE']).to eql 'ACK'
751
+ expect(@rx_pdus[0]['ACK_DIRECTIVE_CODE']).to eql 'EOF'
752
+ expect(@rx_pdus[0]['ACK_DIRECTIVE_SUBTYPE']).to eql 0
753
+ expect(@rx_pdus[0]['CONDITION_CODE']).to eql 'NO_ERROR'
754
+
755
+ expect(@rx_pdus[1]['TYPE']).to eql 'FILE_DIRECTIVE'
756
+ expect(@rx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
757
+ expect(@rx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
758
+ expect(@rx_pdus[1]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
759
+ # 4.6.4.3.4 Finished PDU when complete
760
+ expect(@rx_pdus[1]['DIRECTIVE_CODE']).to eql 'FINISHED'
761
+ expect(@rx_pdus[1]['CONDITION_CODE']).to eql 'NO_ERROR'
762
+ expect(@rx_pdus[1]['DELIVERY_CODE']).to eql 'DATA_COMPLETE'
763
+ expect(@rx_pdus[1]['FILE_STATUS']).to eql 'FILESTORE_SUCCESS'
764
+
765
+ expect(@rx_pdus[2]).to be nil
766
+ end
767
+
768
+ it "detects a single missing data PDU" do
769
+ data = ('a'..'z').to_a.shuffle[0,8].join
770
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
771
+ request(source: 'test1.txt', dest: 'test2.txt',
772
+ transmission_mode: 'ACKNOWLEDGED',
773
+ skip: [1], cycles: 2) do |indications|
774
+ # First the transmit indications
775
+ expect(indications[0]['indication_type']).to eql 'Transaction'
776
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
777
+ # Receive indications
778
+ # 4.6.1.2.6 Metadata-Recv indication
779
+ expect(indications[2]['indication_type']).to eql 'Metadata-Recv'
780
+ expect(indications[2]['source_file_name']).to eql 'test1.txt'
781
+ expect(indications[2]['destination_file_name']).to eql 'test2.txt'
782
+ expect(indications[2]['file_size']).to eql 8
783
+ expect(indications[2]['source_entity_id']).to eql 1
784
+ expect(indications[3]['indication_type']).to eql 'EOF-Recv'
785
+ end
786
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
787
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
788
+
789
+ # Validate the Tx PDUs
790
+ expect(@tx_pdus.length).to eql 5
791
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
792
+ expect(@tx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
793
+ expect(@tx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
794
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
795
+ # 4.6.1.1.1 Acknowledged
796
+ expect(@tx_pdus[0]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
797
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
798
+
799
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
800
+ expect(@tx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
801
+ expect(@tx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
802
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data
803
+ expect(@tx_pdus[1]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
804
+
805
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
806
+ expect(@tx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
807
+ expect(@tx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
808
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
809
+ expect(@tx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
810
+ expect(@tx_pdus[2]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
811
+
812
+ expect(@tx_pdus[3]['TYPE']).to eql 'FILE_DATA'
813
+ expect(@tx_pdus[3]['SOURCE_ENTITY_ID']).to eql @source_entity_id
814
+ expect(@tx_pdus[3]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
815
+ expect(@tx_pdus[3]['FILE_DATA']).to eql data
816
+ expect(@tx_pdus[3]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
817
+
818
+ expect(@tx_pdus[4]['TYPE']).to eql 'FILE_DIRECTIVE'
819
+ expect(@tx_pdus[4]['SOURCE_ENTITY_ID']).to eql @source_entity_id
820
+ expect(@tx_pdus[4]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
821
+ expect(@tx_pdus[4]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
822
+ expect(@tx_pdus[4]['DIRECTIVE_CODE']).to eql 'ACK'
823
+ expect(@tx_pdus[4]['ACK_DIRECTIVE_CODE']).to eql 'FINISHED'
824
+
825
+ # Validate the Rx PDUs
826
+ expect(@rx_pdus.length).to eql 3
827
+ expect(@rx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
828
+ expect(@rx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
829
+ expect(@rx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
830
+ expect(@rx_pdus[0]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
831
+ # 4.6.4.3.5 ACK the EOF
832
+ expect(@rx_pdus[0]['DIRECTIVE_CODE']).to eql 'ACK'
833
+ expect(@rx_pdus[0]['ACK_DIRECTIVE_CODE']).to eql 'EOF'
834
+ expect(@rx_pdus[0]['ACK_DIRECTIVE_SUBTYPE']).to eql 0
835
+ expect(@rx_pdus[0]['CONDITION_CODE']).to eql 'NO_ERROR'
836
+
837
+ expect(@rx_pdus[1]['TYPE']).to eql 'FILE_DIRECTIVE'
838
+ expect(@rx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
839
+ expect(@rx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
840
+ expect(@rx_pdus[1]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
841
+ expect(@rx_pdus[1]['DIRECTIVE_CODE']).to eql 'NAK'
842
+ expect(@rx_pdus[1]['START_OF_SCOPE']).to eql 0
843
+ expect(@rx_pdus[1]['END_OF_SCOPE']).to eql 8
844
+ expect(@rx_pdus[1]['SEGMENT_REQUESTS'].length).to eql 1
845
+ expect(@rx_pdus[1]['SEGMENT_REQUESTS'][0]["START_OFFSET"]).to eql 0
846
+ expect(@rx_pdus[1]['SEGMENT_REQUESTS'][0]["END_OFFSET"]).to eql 8
847
+
848
+ expect(@rx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
849
+ expect(@rx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
850
+ expect(@rx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
851
+ expect(@rx_pdus[2]['TRANSMISSION_MODE']).to eql 'ACKNOWLEDGED'
852
+ # 4.6.4.3.4 Finished PDU when complete
853
+ expect(@rx_pdus[2]['DIRECTIVE_CODE']).to eql 'FINISHED'
854
+ expect(@rx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
855
+ expect(@rx_pdus[2]['DELIVERY_CODE']).to eql 'DATA_COMPLETE'
856
+ expect(@rx_pdus[2]['FILE_STATUS']).to eql 'FILESTORE_SUCCESS'
857
+ end
858
+
859
+ # 4.6.1.2.7 a. Repeated data is discarded
860
+ it "handles repeated data" do
861
+ data = ('a'..'z').to_a.shuffle[0,8].join
862
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
863
+ request(source: 'test1.txt', dest: 'test2.txt',
864
+ duplicate_filedata: true) do |indications|
865
+ # Transmit indications
866
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
867
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
868
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
869
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
870
+ expect(indications[2]['status_report']).to eql 'FINISHED'
871
+ # Receive indications
872
+ expect(indications[6]['indication_type']).to eql 'Transaction-Finished'
873
+ expect(indications[6]['condition_code']).to eql 'NO_ERROR'
874
+ expect(indications[6]['file_status']).to eql 'FILESTORE_SUCCESS'
875
+ expect(indications[6]['delivery_code']).to eql 'DATA_COMPLETE'
876
+ expect(indications[6]['status_report']).to eql 'FINISHED'
877
+ end
878
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
879
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
880
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
881
+
882
+ # Validate the PDUs
883
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
884
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
885
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
886
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data
887
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
888
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
889
+ expect(@tx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
890
+ expect(@rx_pdus[0]).to be nil
891
+ end
892
+
893
+ # 4.6.1.2.7 b. Segementation control flag ... not implemented
894
+
895
+ # 4.6.1.2.7 c. Sum of offset and segment exceeds file size
896
+ it "reports file size error if sum of offset and segment exceeds file size" do
897
+ setup(source_id: 1, destination_id: 2)
898
+ # Disable CRCs so the hacked PDU will be processed
899
+ CfdpMib.set_entity_value(@source_entity_id, 'crcs_required', false)
900
+ CfdpMib.set_entity_value(@destination_entity_id, 'crcs_required', false)
901
+
902
+ data = ('a'..'z').to_a.shuffle[0,9].join
903
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
904
+ request(source: 'test1.txt', dest: 'test2.txt',
905
+ bad_seg_size: true) do |indications|
906
+ # Transmit indications
907
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
908
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
909
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
910
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
911
+ expect(indications[2]['status_report']).to eql 'FINISHED'
912
+ # Receive indications
913
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
914
+ expect(indications[3]['file_size']).to eql 9
915
+ expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
916
+ expect(indications[4]['offset']).to eql 0
917
+ expect(indications[4]['length']).to eql 8
918
+ # 4.6.1.2.7 c. Sum of offset and segment exceeds File Size Error fault
919
+ expect(indications[5]['indication_type']).to eql 'Fault'
920
+ expect(indications[5]['condition_code']).to eql 'FILE_SIZE_ERROR'
921
+ # 11 which is hacked length of 10 plus file size of 1
922
+ expect(indications[5]['progress']).to eql 11
923
+ expect(indications[6]['indication_type']).to eql 'File-Segment-Recv'
924
+ expect(indications[6]['offset']).to eql 10
925
+ expect(indications[6]['length']).to eql 1
926
+ # 4.6.1.2.9 EOF generates File Size Error fault
927
+ expect(indications[7]['indication_type']).to eql 'Fault'
928
+ expect(indications[7]['condition_code']).to eql 'FILE_SIZE_ERROR'
929
+ expect(indications[7]['progress']).to eql 11
930
+ expect(indications[8]['indication_type']).to eql 'EOF-Recv'
931
+ end
932
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be false
933
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
934
+ end
935
+
936
+ # 4.6.1.2.9 progress exceeds file size
937
+ it "reports file size error if progress exceeds file size" do
938
+ setup(source_id: 1, destination_id: 2)
939
+ # Disable CRCs so the hacked PDU will be processed
940
+ CfdpMib.set_entity_value(@source_entity_id, 'crcs_required', false)
941
+ CfdpMib.set_entity_value(@destination_entity_id, 'crcs_required', false)
942
+
943
+ data = ('a'..'z').to_a.shuffle[0,8].join
944
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
945
+ request(source: 'test1.txt', dest: 'test2.txt',
946
+ eof_size: 7) do |indications|
947
+ # Transmit indications
948
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
949
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
950
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
951
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
952
+ expect(indications[2]['status_report']).to eql 'FINISHED'
953
+ # Receive indications
954
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
955
+ expect(indications[3]['file_size']).to eql 8
956
+ expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
957
+ expect(indications[4]['offset']).to eql 0
958
+ expect(indications[4]['length']).to eql 8
959
+ # 4.6.1.2.9 EOF generates File Size Error fault
960
+ expect(indications[5]['indication_type']).to eql 'Fault'
961
+ expect(indications[5]['condition_code']).to eql 'FILE_SIZE_ERROR'
962
+ expect(indications[5]['progress']).to eql 8
963
+ expect(indications[6]['indication_type']).to eql 'EOF-Recv'
964
+ end
965
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be false
966
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
967
+
968
+ # Validate the RX PDU
969
+ expect(@rx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
970
+ expect(@rx_pdus[0]['DIRECTIVE_CODE']).to eql 'NAK'
971
+ expect(@rx_pdus[0]['START_OF_SCOPE']).to eql 0
972
+ expect(@rx_pdus[0]['END_OF_SCOPE']).to eql 7 # Our bad eof_size
973
+ expect(@rx_pdus[0]['SEGMENT_REQUESTS']).to eql [] # TODO: Nothing?
974
+ end
975
+
976
+ # 4.6.1.2.10 Flow label optional & implementation specific ... not implemented
977
+
978
+ # # 4.6.4.2.1 Respond to NAKs by retransmitting
979
+ # it "responds to NAKs by retransmitting" do
980
+ # data = ('a'..'z').to_a.shuffle[0,8].join
981
+ # File.write(File.join(SPEC_DIR, 'test1.txt'), data)
982
+ # request(source: 'test1.txt', dest: 'test2.txt',
983
+ # transmission_mode: 'ACKNOWLEDGED',
984
+ # skip: [1],
985
+ # prompt: 'NAK') do |indications|
986
+ # pp indications
987
+ # # Transmit indications
988
+ # # expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
989
+ # # expect(indications[2]['condition_code']).to eql 'NO_ERROR'
990
+ # # expect(indications[2]['file_status']).to eql 'UNREPORTED'
991
+ # # expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
992
+ # # expect(indications[2]['status_report']).to eql 'FINISHED'
993
+ # # # Receive indications
994
+ # # expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
995
+ # # expect(indications[3]['file_size']).to eql '8'
996
+ # # expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
997
+ # # expect(indications[4]['offset']).to eql '0'
998
+ # # expect(indications[4]['length']).to eql '8'
999
+ # # # 4.6.1.2.9 EOF generates File Size Error fault
1000
+ # # expect(indications[5]['indication_type']).to eql 'Fault'
1001
+ # # expect(indications[5]['condition_code']).to eql 'FILE_SIZE_ERROR'
1002
+ # # expect(indications[5]['progress']).to eql '8'
1003
+ # # expect(indications[6]['indication_type']).to eql 'EOF-Recv'
1004
+ # end
1005
+ # # expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be false
1006
+ # # FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
1007
+ # pp @tx_pdus
1008
+ # pp @rx_pdus
1009
+ # end
1010
+
1011
+ # 4.6.4.3.2 Lost Metadata
1012
+ # TODO: Send a transfer but not the metadata and check the NAK for 0-0
1013
+
1014
+ # Test various segments missing
1015
+ # 4.6.4.3.1 File data gap
1016
+ %w(1 2 3 1_2 2_3 1_3).each do |missing|
1017
+ context "with segment #{missing} missing" do
1018
+ it "sends a NAK" do
1019
+ setup(source_id: 1, destination_id: 2)
1020
+ # Ignore all the errors about no such file "test1.txt"
1021
+ # that happen because of the NAK
1022
+ allow(OpenC3::Logger).to receive(:error)
1023
+ data = ('a'..'z').to_a.shuffle[0,17].join
1024
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
1025
+ segments = missing.split('_')
1026
+ segments.map! {|segment| segment.to_i }
1027
+ start_offset = nil
1028
+ end_offset = nil
1029
+ if segments.include?(3)
1030
+ start_offset = 16
1031
+ end
1032
+ if segments.include?(2)
1033
+ start_offset = 8
1034
+ end
1035
+ if segments.include?(1)
1036
+ start_offset = 0
1037
+ end_offset = 8
1038
+ end
1039
+ if segments.include?(2)
1040
+ end_offset = 16
1041
+ end
1042
+ if segments.include?(3)
1043
+ end_offset = 17
1044
+ end
1045
+ request(source: 'test1.txt', dest: 'test2.txt', skip: segments) do |indications|
1046
+ # First the transmit indications
1047
+ expect(indications[0]['indication_type']).to eql 'Transaction'
1048
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
1049
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
1050
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
1051
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
1052
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
1053
+ expect(indications[2]['status_report']).to eql 'FINISHED'
1054
+ # Receive indications
1055
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
1056
+ expect(indications[3]['source_file_name']).to eql 'test1.txt'
1057
+ expect(indications[3]['destination_file_name']).to eql 'test2.txt'
1058
+ expect(indications[3]['file_size']).to eql 17
1059
+ expect(indications[3]['source_entity_id']).to eql 1
1060
+ # Segment indications
1061
+ i = 4
1062
+ unless segments.include?(1)
1063
+ expect(indications[i]['indication_type']).to eql 'File-Segment-Recv'
1064
+ expect(indications[i]['offset']).to eql 0
1065
+ expect(indications[i]['length']).to eql 8
1066
+ i += 1
1067
+ end
1068
+ unless segments.include?(2)
1069
+ expect(indications[i]['indication_type']).to eql 'File-Segment-Recv'
1070
+ expect(indications[i]['offset']).to eql 8
1071
+ expect(indications[i]['length']).to eql 8
1072
+ i += 1
1073
+ end
1074
+ unless segments.include?(3)
1075
+ expect(indications[i]['indication_type']).to eql 'File-Segment-Recv'
1076
+ expect(indications[i]['offset']).to eql 16
1077
+ expect(indications[i]['length']).to eql 1
1078
+ i += 1
1079
+ end
1080
+ expect(indications[i]['indication_type']).to eql 'EOF-Recv'
1081
+ end
1082
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be false
1083
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
1084
+
1085
+ # Validate the TX PDUs
1086
+ expect(@tx_pdus.length).to eql (5 + segments.length)
1087
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
1088
+ expect(@tx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1089
+ expect(@tx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1090
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
1091
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
1092
+
1093
+ # Even though a FILE_DATA pdu is skipped by receive it is still sent
1094
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
1095
+ expect(@tx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1096
+ expect(@tx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1097
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data[0..7]
1098
+
1099
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DATA'
1100
+ expect(@tx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1101
+ expect(@tx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1102
+ expect(@tx_pdus[2]['FILE_DATA']).to eql data[8..15]
1103
+
1104
+ expect(@tx_pdus[3]['TYPE']).to eql 'FILE_DATA'
1105
+ expect(@tx_pdus[3]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1106
+ expect(@tx_pdus[3]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1107
+ expect(@tx_pdus[3]['FILE_DATA']).to eql data[16..-1]
1108
+
1109
+ expect(@tx_pdus[4]['TYPE']).to eql 'FILE_DIRECTIVE'
1110
+ expect(@tx_pdus[4]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1111
+ expect(@tx_pdus[4]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1112
+ expect(@tx_pdus[4]['DIRECTIVE_CODE']).to eql 'EOF'
1113
+ expect(@tx_pdus[4]['CONDITION_CODE']).to eql 'NO_ERROR'
1114
+
1115
+ index = 0
1116
+ segments.each do |segment|
1117
+ expect(@tx_pdus[5 + index]['TYPE']).to eql 'FILE_DATA'
1118
+ expect(@tx_pdus[5 + index]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1119
+ expect(@tx_pdus[5 + index]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1120
+ if segment == 1
1121
+ expect(@tx_pdus[5 + index]['FILE_DATA']).to eql data[0..7]
1122
+ elsif segment == 2
1123
+ expect(@tx_pdus[5 + index]['FILE_DATA']).to eql data[8..15]
1124
+ else
1125
+ expect(@tx_pdus[5 + index]['FILE_DATA']).to eql data[16..-1]
1126
+ end
1127
+ index += 1
1128
+ end
1129
+
1130
+ # Validate the RX PDUs
1131
+ expect(@rx_pdus.length).to eql 1
1132
+ expect(@rx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
1133
+ expect(@rx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1134
+ expect(@rx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1135
+ # 4.6.4.4.2 Immediate NAK mode
1136
+ expect(@rx_pdus[0]['DIRECTIVE_CODE']).to eql 'NAK'
1137
+ # 4.6.4.3.3 a Scope of the NAK is start and end offset
1138
+ expect(@rx_pdus[0]['START_OF_SCOPE']).to eql 0
1139
+ expect(@rx_pdus[0]['END_OF_SCOPE']).to eql 17
1140
+ # 4.6.4.3.3 b Segment requests should indicate only the segments not yet received
1141
+ if segments == [1,3] # Special case
1142
+ expect(@rx_pdus[0]['SEGMENT_REQUESTS']).to eql [{"START_OFFSET"=>0, "END_OFFSET"=>8}, {"START_OFFSET"=>16, "END_OFFSET"=>17}]
1143
+ else
1144
+ expect(@rx_pdus[0]['SEGMENT_REQUESTS']).to eql [{"START_OFFSET"=>start_offset, "END_OFFSET"=>end_offset}]
1145
+ end
1146
+ end
1147
+ end
1148
+ end
1149
+
1150
+ # 4.6.5.2.2 Send Keep Alive as Prompt response
1151
+ it "sends keep alive response to prompt" do
1152
+ data = ('a'..'z').to_a.shuffle[0,8].join
1153
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
1154
+ request(source: 'test1.txt', dest: 'test2.txt',
1155
+ transmission_mode: 'ACKNOWLEDGED',
1156
+ prompt: 'KEEP_ALIVE') do |indications|
1157
+ # First the transmit indications
1158
+ expect(indications[0]['indication_type']).to eql 'Transaction'
1159
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
1160
+ # Receive indications
1161
+ expect(indications[2]['indication_type']).to eql 'Metadata-Recv'
1162
+ expect(indications[3]['indication_type']).to eql 'File-Segment-Recv'
1163
+ expect(indications[4]['indication_type']).to eql 'EOF-Recv'
1164
+ expect(indications[5]['indication_type']).to eql 'Transaction-Finished'
1165
+ expect(indications[5]['condition_code']).to eql 'NO_ERROR'
1166
+ expect(indications[5]['file_status']).to eql 'FILESTORE_SUCCESS'
1167
+ expect(indications[5]['delivery_code']).to eql 'DATA_COMPLETE'
1168
+ expect(indications[5]['status_report']).to eql 'FINISHED'
1169
+ # Final transmit
1170
+ expect(indications[6]['indication_type']).to eql 'Transaction-Finished'
1171
+ expect(indications[6]['condition_code']).to eql 'NO_ERROR'
1172
+ expect(indications[6]['file_status']).to eql 'FILESTORE_SUCCESS'
1173
+ expect(indications[6]['delivery_code']).to eql 'DATA_COMPLETE'
1174
+ expect(indications[6]['status_report']).to eql 'FINISHED'
1175
+ end
1176
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
1177
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
1178
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
1179
+
1180
+ # Validate the TX PDUs
1181
+ expect(@tx_pdus.length).to eql 4
1182
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
1183
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
1184
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
1185
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
1186
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
1187
+ expect(@tx_pdus[3]['TYPE']).to eql 'FILE_DIRECTIVE'
1188
+ expect(@tx_pdus[3]['DIRECTIVE_CODE']).to eql 'ACK'
1189
+
1190
+ # Validate the RX PDUs
1191
+ expect(@rx_pdus.length).to eql 3
1192
+ expect(@rx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
1193
+ expect(@rx_pdus[0]['DIRECTIVE_CODE']).to eql 'KEEP_ALIVE'
1194
+ expect(@rx_pdus[0]['PROGRESS']).to eql 8
1195
+ expect(@rx_pdus[1]['TYPE']).to eql 'FILE_DIRECTIVE'
1196
+ expect(@rx_pdus[1]['DIRECTIVE_CODE']).to eql 'ACK'
1197
+ expect(@rx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
1198
+ expect(@rx_pdus[2]['DIRECTIVE_CODE']).to eql 'FINISHED'
1199
+ end
1200
+
1201
+ it "waits for a closure" do
1202
+ data = ('a'..'z').to_a.shuffle[0,8].join
1203
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
1204
+ request(source: 'test1.txt', dest: 'test2.txt', closure: 'CLOSURE_REQUESTED') do |indications|
1205
+ # First the transmit indications
1206
+ expect(indications[0]['indication_type']).to eql 'Transaction'
1207
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
1208
+ # Receive indications
1209
+ expect(indications[2]['indication_type']).to eql 'Metadata-Recv'
1210
+ expect(indications[2]['source_file_name']).to eql 'test1.txt'
1211
+ expect(indications[2]['destination_file_name']).to eql 'test2.txt'
1212
+ expect(indications[2]['file_size']).to eql 8
1213
+ expect(indications[2]['source_entity_id']).to eql 1
1214
+ expect(indications[3]['indication_type']).to eql 'File-Segment-Recv'
1215
+ expect(indications[3]['offset']).to eql 0
1216
+ expect(indications[3]['length']).to eql 8
1217
+ expect(indications[4]['indication_type']).to eql 'EOF-Recv'
1218
+ # 4.6.3.2.3 Notice of completion
1219
+ expect(indications[5]['indication_type']).to eql 'Transaction-Finished'
1220
+ expect(indications[5]['condition_code']).to eql 'NO_ERROR'
1221
+ expect(indications[5]['file_status']).to eql 'FILESTORE_SUCCESS'
1222
+ expect(indications[5]['delivery_code']).to eql 'DATA_COMPLETE'
1223
+ expect(indications[5]['status_report']).to eql 'FINISHED'
1224
+ # Final transmit
1225
+ expect(indications[6]['indication_type']).to eql 'Transaction-Finished'
1226
+ expect(indications[6]['condition_code']).to eql 'NO_ERROR'
1227
+ expect(indications[6]['file_status']).to eql 'FILESTORE_SUCCESS'
1228
+ expect(indications[6]['delivery_code']).to eql 'DATA_COMPLETE'
1229
+ expect(indications[6]['status_report']).to eql 'FINISHED'
1230
+ end
1231
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
1232
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
1233
+
1234
+ # Validate the PDUs
1235
+ expect(@tx_pdus.length).to eql 3
1236
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
1237
+ expect(@tx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1238
+ expect(@tx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1239
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
1240
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
1241
+
1242
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
1243
+ expect(@tx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1244
+ expect(@tx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1245
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data[0..7]
1246
+
1247
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
1248
+ expect(@tx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1249
+ expect(@tx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1250
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
1251
+ expect(@tx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
1252
+
1253
+ expect(@rx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
1254
+ expect(@rx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1255
+ expect(@rx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1256
+ expect(@rx_pdus[0]['DIRECTIVE_CODE']).to eql 'FINISHED'
1257
+ expect(@rx_pdus[0]['CONDITION_CODE']).to eql 'NO_ERROR'
1258
+ expect(@rx_pdus[0]['DELIVERY_CODE']).to eql 'DATA_COMPLETE'
1259
+ expect(@rx_pdus[0]['FILE_STATUS']).to eql 'FILESTORE_SUCCESS'
1260
+ end
1261
+
1262
+ # 4.6.3.2.2 transaction check timer
1263
+ it "times out waiting for a closure" do
1264
+ data = ('a'..'z').to_a.shuffle[0,8].join
1265
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
1266
+ setup(source_id: 1, destination_id: 2)
1267
+ CfdpMib.set_entity_value(@source_entity_id, 'check_interval', 0.1)
1268
+ request(source: 'test1.txt', dest: 'test2.txt',
1269
+ closure: 'CLOSURE_REQUESTED', send_closure: false) do |indications|
1270
+ # First the transmit indications
1271
+ expect(indications[0]['indication_type']).to eql 'Transaction'
1272
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
1273
+ # Receive indications
1274
+ expect(indications[2]['indication_type']).to eql 'Metadata-Recv'
1275
+ expect(indications[2]['source_file_name']).to eql 'test1.txt'
1276
+ expect(indications[2]['destination_file_name']).to eql 'test2.txt'
1277
+ expect(indications[2]['file_size']).to eql 8
1278
+ expect(indications[2]['source_entity_id']).to eql 1
1279
+ expect(indications[3]['indication_type']).to eql 'File-Segment-Recv'
1280
+ expect(indications[3]['offset']).to eql 0
1281
+ expect(indications[3]['length']).to eql 8
1282
+ expect(indications[4]['indication_type']).to eql 'EOF-Recv'
1283
+ expect(indications[5]['indication_type']).to eql 'Transaction-Finished'
1284
+ expect(indications[5]['condition_code']).to eql 'NO_ERROR'
1285
+ expect(indications[5]['file_status']).to eql 'FILESTORE_SUCCESS'
1286
+ expect(indications[5]['delivery_code']).to eql 'DATA_COMPLETE'
1287
+ expect(indications[5]['status_report']).to eql 'FINISHED'
1288
+ # 4.6.3.2.4 Check limit reached fault
1289
+ expect(indications[6]['indication_type']).to eql 'Fault'
1290
+ expect(indications[6]['condition_code']).to eql 'CHECK_LIMIT_REACHED'
1291
+ expect(indications[7]['indication_type']).to eql 'Transaction-Finished'
1292
+ expect(indications[7]['condition_code']).to eql 'CHECK_LIMIT_REACHED'
1293
+ expect(indications[7]['file_status']).to eql 'UNREPORTED'
1294
+ expect(indications[7]['delivery_code']).to eql 'DATA_COMPLETE'
1295
+ expect(indications[7]['status_report']).to eql 'FINISHED'
1296
+ end
1297
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
1298
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
1299
+ end
1300
+
1301
+ it "handles bad transaction IDs" do
1302
+ setup(source_id: 11, destination_id: 22)
1303
+
1304
+ @user = CfdpUser.new
1305
+ thread = @user.start
1306
+ sleep 0.1 # Allow user thread to start
1307
+ expect(thread.alive?).to be true
1308
+
1309
+ cmd_params = {}
1310
+ cmd_params["PDU"] = CfdpPdu.build_eof_pdu(
1311
+ source_entity: CfdpMib.entity(@destination_entity_id),
1312
+ transaction_seq_num: 1,
1313
+ destination_entity: CfdpMib.entity(@source_entity_id),
1314
+ file_size: 8,
1315
+ file_checksum: 0,
1316
+ condition_code: "NO_ERROR",
1317
+ segmentation_control: "NOT_PRESERVED",
1318
+ transmission_mode: nil,
1319
+ canceling_entity_id: nil)
1320
+ msg_hash = {
1321
+ :time => Time.now.to_nsec_from_epoch,
1322
+ :stored => 'false',
1323
+ :target_name => "CFDPTEST",
1324
+ :packet_name => "CFDP_PDU",
1325
+ :received_count => 1,
1326
+ :json_data => JSON.generate(cmd_params.as_json(:allow_nan => true)),
1327
+ }
1328
+ error_message = ''
1329
+ allow(OpenC3::Logger).to receive(:error) do |msg|
1330
+ error_message = msg
1331
+ end
1332
+
1333
+ safe_write_topic("DEFAULT__DECOM__{CFDPTEST}__CFDP_PDU", msg_hash, nil)
1334
+ sleep 0.1
1335
+ # Thread is still running even after the bad transaction
1336
+ expect(thread.alive?).to be true
1337
+ expect(error_message).to include("Unknown transaction")
1338
+ @user.stop
1339
+ sleep 0.1
1340
+ end
1341
+
1342
+ it "cancels a transaction" do
1343
+ data = ('a'..'z').to_a.shuffle[0,8].join
1344
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
1345
+ # This cancels the receive transaction ... how to cancel the source transaction?
1346
+ request(source: 'test1.txt', dest: 'test2.txt', cancel: true) do |indications|
1347
+ # First the transmit indications
1348
+ expect(indications[0]['indication_type']).to eql 'Transaction'
1349
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
1350
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
1351
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
1352
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
1353
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
1354
+ expect(indications[2]['status_report']).to eql 'FINISHED'
1355
+ # Receive indications
1356
+ expect(indications[-1]['condition_code']).to eql 'CANCEL_REQUEST_RECEIVED'
1357
+ expect(indications[-1]['file_status']).to eql 'FILESTORE_SUCCESS'
1358
+ expect(indications[-1]['delivery_code']).to eql 'DATA_COMPLETE'
1359
+ expect(indications[-1]['status_report']).to eql 'CANCELED'
1360
+ end
1361
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
1362
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
1363
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
1364
+
1365
+ # Validate the PDUs
1366
+ expect(@tx_pdus.length).to eql 3
1367
+ expect(@tx_pdus[0]['TYPE']).to eql 'FILE_DIRECTIVE'
1368
+ expect(@tx_pdus[0]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1369
+ expect(@tx_pdus[0]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1370
+ expect(@tx_pdus[0]['DIRECTIVE_CODE']).to eql 'METADATA'
1371
+ expect(@tx_pdus[0]['FILE_SIZE']).to eql data.length
1372
+
1373
+ expect(@tx_pdus[1]['TYPE']).to eql 'FILE_DATA'
1374
+ expect(@tx_pdus[1]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1375
+ expect(@tx_pdus[1]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1376
+ expect(@tx_pdus[1]['FILE_DATA']).to eql data
1377
+
1378
+ expect(@tx_pdus[2]['TYPE']).to eql 'FILE_DIRECTIVE'
1379
+ expect(@tx_pdus[2]['SOURCE_ENTITY_ID']).to eql @source_entity_id
1380
+ expect(@tx_pdus[2]['DESTINATION_ENTITY_ID']).to eql @destination_entity_id
1381
+ expect(@tx_pdus[2]['DIRECTIVE_CODE']).to eql 'EOF'
1382
+ expect(@tx_pdus[2]['CONDITION_CODE']).to eql 'NO_ERROR'
1383
+ end
1384
+
1385
+ it "runs filestore requests after copy" do
1386
+ data = ('a'..'z').to_a.shuffle[0,8].join
1387
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
1388
+ request(source: 'test1.txt', dest: 'test2.txt',
1389
+ requests: [
1390
+ ['CREATE_FILE', 'new_file.txt']
1391
+ ]) do |indications|
1392
+ # First the transmit indications
1393
+ expect(indications[0]['indication_type']).to eql 'Transaction'
1394
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
1395
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
1396
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
1397
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
1398
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
1399
+ expect(indications[2]['status_report']).to eql 'FINISHED'
1400
+ # Receive indications
1401
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
1402
+ expect(indications[3]['source_file_name']).to eql 'test1.txt'
1403
+ expect(indications[3]['destination_file_name']).to eql 'test2.txt'
1404
+ expect(indications[3]['file_size']).to eql 8
1405
+ expect(indications[3]['source_entity_id']).to eql 1
1406
+ expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
1407
+ expect(indications[4]['offset']).to eql 0
1408
+ expect(indications[4]['length']).to eql 8
1409
+ expect(indications[5]['indication_type']).to eql 'EOF-Recv'
1410
+ expect(indications[6]['indication_type']).to eql 'Transaction-Finished'
1411
+ expect(indications[6]['condition_code']).to eql 'NO_ERROR'
1412
+ expect(indications[6]['file_status']).to eql 'FILESTORE_SUCCESS'
1413
+ expect(indications[6]['delivery_code']).to eql 'DATA_COMPLETE'
1414
+ expect(indications[6]['status_report']).to eql 'FINISHED'
1415
+ fsr = indications[6]['filestore_responses'][0]
1416
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_FILE'
1417
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1418
+ expect(fsr['FIRST_FILE_NAME']).to eql 'new_file.txt'
1419
+ end
1420
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be true
1421
+ expect(File.exist?(File.join(SPEC_DIR, 'new_file.txt'))).to be true
1422
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true # force ignores error if file doesn't exist
1423
+ FileUtils.rm File.join(SPEC_DIR, 'test2.txt')
1424
+ FileUtils.rm File.join(SPEC_DIR, 'new_file.txt')
1425
+ end
1426
+
1427
+ it "skips filestore requests if copy fails" do
1428
+ # Simulate a failure
1429
+ allow(CfdpMib).to receive(:put_destination_file).and_return(false)
1430
+ data = ('a'..'z').to_a.shuffle[0,8].join
1431
+ File.write(File.join(SPEC_DIR, 'test1.txt'), data)
1432
+ request(source: 'test1.txt', dest: 'test2.txt',
1433
+ requests: [
1434
+ ['CREATE_FILE', 'new_file.txt']
1435
+ ]) do |indications|
1436
+ # First the transmit indications
1437
+ expect(indications[0]['indication_type']).to eql 'Transaction'
1438
+ expect(indications[1]['indication_type']).to eql 'EOF-Sent'
1439
+ expect(indications[2]['indication_type']).to eql 'Transaction-Finished'
1440
+ expect(indications[2]['condition_code']).to eql 'NO_ERROR'
1441
+ expect(indications[2]['file_status']).to eql 'UNREPORTED'
1442
+ expect(indications[2]['delivery_code']).to eql 'DATA_COMPLETE'
1443
+ expect(indications[2]['status_report']).to eql 'FINISHED'
1444
+ # Receive indications
1445
+ expect(indications[3]['indication_type']).to eql 'Metadata-Recv'
1446
+ expect(indications[3]['source_file_name']).to eql 'test1.txt'
1447
+ expect(indications[3]['destination_file_name']).to eql 'test2.txt'
1448
+ expect(indications[3]['file_size']).to eql 8
1449
+ expect(indications[3]['source_entity_id']).to eql 1
1450
+ expect(indications[4]['indication_type']).to eql 'File-Segment-Recv'
1451
+ expect(indications[4]['offset']).to eql 0
1452
+ expect(indications[4]['length']).to eql 8
1453
+ expect(indications[5]['indication_type']).to eql 'EOF-Recv'
1454
+ # 4.6.1.2.5 Filestore Rejection Fault
1455
+ expect(indications[6]['indication_type']).to eql 'Fault'
1456
+ expect(indications[6]['condition_code']).to eql 'FILESTORE_REJECTION'
1457
+ expect(indications[7]['indication_type']).to eql 'Transaction-Finished'
1458
+ expect(indications[7]['condition_code']).to eql 'FILESTORE_REJECTION'
1459
+ expect(indications[7]['file_status']).to eql 'FILESTORE_REJECTION'
1460
+ expect(indications[7]['delivery_code']).to eql 'DATA_COMPLETE'
1461
+ expect(indications[7]['status_report']).to eql 'FINISHED'
1462
+ expect(indications[7]['filestore_responses']).to be nil
1463
+ end
1464
+ expect(File.exist?(File.join(SPEC_DIR, 'test2.txt'))).to be false
1465
+ expect(File.exist?(File.join(SPEC_DIR, 'new_file.txt'))).to be false
1466
+ FileUtils.rm File.join(SPEC_DIR, 'test1.txt'), force: true
1467
+ end
1468
+
1469
+ %w(local bucket).each do |type|
1470
+ context "with #{type} filestore requests" do
1471
+ if type == 'bucket'
1472
+ # Enable if there's an actual MINIO service avaiable to talk to
1473
+ # To enable access to MINIO for testing change the compose.yaml file and add
1474
+ # the following to services: open3-minio:
1475
+ # ports:
1476
+ # - "127.0.0.1:9000:9000"
1477
+ if ENV['MINIO']
1478
+ before(:all) do
1479
+ @bucket = OpenC3::Bucket.getClient.create("bucket#{rand(1000)}")
1480
+ @root_path = 'path'
1481
+ end
1482
+ after(:all) do
1483
+ OpenC3::Bucket.getClient.delete(@bucket) if @bucket
1484
+ end
1485
+ else
1486
+ # Simulate the bucket by stubbing out the bucket client
1487
+ before(:each) do
1488
+ @client = double("getClient").as_null_object
1489
+ allow(@client).to receive(:exist?).and_return(true)
1490
+ allow(@client).to receive(:get_object) do |bucket:, key:, path:|
1491
+ File.write(path, File.read(key))
1492
+ end
1493
+ allow(@client).to receive(:put_object) do |bucket:, key:, body:|
1494
+ File.write(key, body)
1495
+ end
1496
+ allow(@client).to receive(:check_object) do |bucket:, key:|
1497
+ File.exist?(key)
1498
+ end
1499
+ allow(@client).to receive(:delete_object) do |bucket:, key:|
1500
+ FileUtils.rm(key)
1501
+ end
1502
+ allow(OpenC3::Bucket).to receive(:getClient).and_return(@client)
1503
+ @root_path = SPEC_DIR
1504
+ @bucket = 'config'
1505
+ end
1506
+ end
1507
+ end
1508
+
1509
+ it "create file" do
1510
+ request(requests: [
1511
+ ['CREATE_FILE', "create_file.txt"],
1512
+ ['CREATE_FILE', "../nope"], # Outside of the root path
1513
+ ['CREATE_FILE', "another_file.txt"],
1514
+ ]) do |indications|
1515
+ indication = indications[-1]
1516
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1517
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1518
+ expect(indication['file_status']).to eql 'UNREPORTED'
1519
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1520
+ expect(indication['status_report']).to eql 'FINISHED'
1521
+ fsr = indication['filestore_responses'][0]
1522
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_FILE'
1523
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1524
+ expect(fsr['FIRST_FILE_NAME']).to eql 'create_file.txt'
1525
+ fsr = indication['filestore_responses'][1]
1526
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_FILE'
1527
+ expect(fsr['STATUS_CODE']).to eql 'NOT_ALLOWED'
1528
+ expect(fsr['FIRST_FILE_NAME']).to eql '../nope'
1529
+ fsr = indication['filestore_responses'][2]
1530
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_FILE'
1531
+ # Once there is a failure no more are performed per 4.9.5
1532
+ expect(fsr['STATUS_CODE']).to eql 'NOT_PERFORMED'
1533
+ expect(fsr['FIRST_FILE_NAME']).to eql 'another_file.txt'
1534
+ end
1535
+ if ENV['MINIO'] && type == 'bucket'
1536
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'create_file.txt'))
1537
+ else
1538
+ FileUtils.rm File.join(SPEC_DIR, 'create_file.txt') # cleanup
1539
+ end
1540
+ end
1541
+
1542
+ it "delete file" do
1543
+ request(requests: [
1544
+ ['CREATE_FILE', 'delete_file.txt'],
1545
+ ['DELETE_FILE', 'delete_file.txt'],
1546
+ ['DELETE_FILE', 'nope'],
1547
+ ['DELETE_FILE', 'another'],
1548
+ ]) do |indications|
1549
+ indication = indications[-1]
1550
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1551
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1552
+ expect(indication['file_status']).to eql 'UNREPORTED'
1553
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1554
+ expect(indication['status_report']).to eql 'FINISHED'
1555
+ fsr = indication['filestore_responses'][0]
1556
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_FILE'
1557
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1558
+ expect(fsr['FIRST_FILE_NAME']).to eql 'delete_file.txt'
1559
+ fsr = indication['filestore_responses'][1]
1560
+ expect(fsr['ACTION_CODE']).to eql 'DELETE_FILE'
1561
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1562
+ expect(fsr['FIRST_FILE_NAME']).to eql 'delete_file.txt'
1563
+ fsr = indication['filestore_responses'][2]
1564
+ expect(fsr['ACTION_CODE']).to eql 'DELETE_FILE'
1565
+ expect(fsr['STATUS_CODE']).to eql 'FILE_DOES_NOT_EXIST'
1566
+ expect(fsr['FIRST_FILE_NAME']).to eql 'nope'
1567
+ fsr = indication['filestore_responses'][3]
1568
+ expect(fsr['ACTION_CODE']).to eql 'DELETE_FILE'
1569
+ # Once there is a failure no more are performed per 4.9.5
1570
+ expect(fsr['STATUS_CODE']).to eql 'NOT_PERFORMED'
1571
+ expect(fsr['FIRST_FILE_NAME']).to eql 'another'
1572
+ end
1573
+ if ENV['MINIO'] && type == 'bucket'
1574
+ expect(OpenC3::Bucket.getClient().check_object(bucket: @bucket, key: File.join(@root_path, 'delete_file.txt'))).to be false
1575
+ else
1576
+ expect(File.exist?(File.join(SPEC_DIR, 'delete_file.txt'))).to be false
1577
+ end
1578
+ end
1579
+
1580
+ it "rename file" do
1581
+ request(requests: [
1582
+ ['CREATE_FILE', 'rename_file.txt'],
1583
+ ['RENAME_FILE', 'rename_file.txt', 'new_file.txt'],
1584
+ ['RENAME_FILE', 'nope', 'whatever'],
1585
+ ['RENAME_FILE', 'another'],
1586
+ ]) do |indications|
1587
+ indication = indications[-1]
1588
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1589
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1590
+ expect(indication['file_status']).to eql 'UNREPORTED'
1591
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1592
+ expect(indication['status_report']).to eql 'FINISHED'
1593
+ fsr = indication['filestore_responses'][0]
1594
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_FILE'
1595
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1596
+ expect(fsr['FIRST_FILE_NAME']).to eql 'rename_file.txt'
1597
+ fsr = indication['filestore_responses'][1]
1598
+ expect(fsr['ACTION_CODE']).to eql 'RENAME_FILE'
1599
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1600
+ expect(fsr['FIRST_FILE_NAME']).to eql 'rename_file.txt'
1601
+ expect(fsr['SECOND_FILE_NAME']).to eql 'new_file.txt'
1602
+ fsr = indication['filestore_responses'][2]
1603
+ expect(fsr['ACTION_CODE']).to eql 'RENAME_FILE'
1604
+ expect(fsr['STATUS_CODE']).to eql 'OLD_FILE_DOES_NOT_EXIST'
1605
+ expect(fsr['FIRST_FILE_NAME']).to eql 'nope'
1606
+ expect(fsr['SECOND_FILE_NAME']).to eql 'whatever'
1607
+ fsr = indication['filestore_responses'][3]
1608
+ expect(fsr['ACTION_CODE']).to eql 'RENAME_FILE'
1609
+ # Once there is a failure no more are performed per 4.9.5
1610
+ expect(fsr['STATUS_CODE']).to eql 'NOT_PERFORMED'
1611
+ expect(fsr['FIRST_FILE_NAME']).to eql 'another'
1612
+ end
1613
+ if ENV['MINIO'] && type == 'bucket'
1614
+ expect(OpenC3::Bucket.getClient().check_object(bucket: @bucket, key: File.join(@root_path, 'rename_file.txt'))).to be false
1615
+ expect(OpenC3::Bucket.getClient().check_object(bucket: @bucket, key: File.join(@root_path, 'new_file.txt'))).to be true
1616
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'new_file.txt'))
1617
+ else
1618
+ expect(File.exist?(File.join(SPEC_DIR, 'rename_file.txt'))).to be false
1619
+ expect(File.exist?(File.join(SPEC_DIR, 'new_file.txt'))).to be true
1620
+ FileUtils.rm File.join(SPEC_DIR, 'new_file.txt')
1621
+ end
1622
+ end
1623
+
1624
+ it "rename file error" do
1625
+ request(requests: [
1626
+ ['CREATE_FILE', 'rename_file.txt'],
1627
+ ['RENAME_FILE', 'rename_file.txt', 'rename_file.txt'],
1628
+ ]) do |indications|
1629
+ indication = indications[-1]
1630
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1631
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1632
+ expect(indication['file_status']).to eql 'UNREPORTED'
1633
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1634
+ expect(indication['status_report']).to eql 'FINISHED'
1635
+ fsr = indication['filestore_responses'][0]
1636
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_FILE'
1637
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1638
+ expect(fsr['FIRST_FILE_NAME']).to eql 'rename_file.txt'
1639
+ fsr = indication['filestore_responses'][1]
1640
+ expect(fsr['ACTION_CODE']).to eql 'RENAME_FILE'
1641
+ expect(fsr['STATUS_CODE']).to eql 'NEW_FILE_ALREADY_EXISTS'
1642
+ expect(fsr['FIRST_FILE_NAME']).to eql 'rename_file.txt'
1643
+ expect(fsr['SECOND_FILE_NAME']).to eql 'rename_file.txt'
1644
+ end
1645
+ if ENV['MINIO'] && type == 'bucket'
1646
+ expect(OpenC3::Bucket.getClient().check_object(bucket: @bucket, key: File.join(@root_path, 'rename_file.txt'))).to be true
1647
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'rename_file.txt'))
1648
+ else
1649
+ expect(File.exist?(File.join(SPEC_DIR, 'rename_file.txt'))).to be true
1650
+ FileUtils.rm File.join(SPEC_DIR, 'rename_file.txt')
1651
+ end
1652
+ end
1653
+
1654
+ it "append file" do
1655
+ if ENV['MINIO'] && type == 'bucket'
1656
+ OpenC3::Bucket.getClient().put_object(bucket: @bucket, key: File.join(@root_path, 'first.txt'), body: 'FIRST')
1657
+ OpenC3::Bucket.getClient().put_object(bucket: @bucket, key: File.join(@root_path, 'second.txt'), body: 'SECOND')
1658
+ else
1659
+ File.write(File.join(SPEC_DIR, 'first.txt'), 'FIRST')
1660
+ File.write(File.join(SPEC_DIR, 'second.txt'), 'SECOND')
1661
+ end
1662
+ request(requests: [
1663
+ ['APPEND_FILE', 'first.txt', 'second.txt'],
1664
+ ['APPEND_FILE', 'nope', 'second.txt'],
1665
+ ['APPEND_FILE', 'another'],
1666
+ ]) do |indications|
1667
+ indication = indications[-1]
1668
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1669
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1670
+ expect(indication['file_status']).to eql 'UNREPORTED'
1671
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1672
+ expect(indication['status_report']).to eql 'FINISHED'
1673
+ fsr = indication['filestore_responses'][0]
1674
+ expect(fsr['ACTION_CODE']).to eql 'APPEND_FILE'
1675
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1676
+ expect(fsr['FIRST_FILE_NAME']).to eql 'first.txt'
1677
+ expect(fsr['SECOND_FILE_NAME']).to eql 'second.txt'
1678
+ fsr = indication['filestore_responses'][1]
1679
+ expect(fsr['ACTION_CODE']).to eql 'APPEND_FILE'
1680
+ expect(fsr['STATUS_CODE']).to eql 'FILE_1_DOES_NOT_EXIST'
1681
+ expect(fsr['FIRST_FILE_NAME']).to eql 'nope'
1682
+ expect(fsr['SECOND_FILE_NAME']).to eql 'second.txt'
1683
+ fsr = indication['filestore_responses'][2]
1684
+ expect(fsr['ACTION_CODE']).to eql 'APPEND_FILE'
1685
+ # Once there is a failure no more are performed per 4.9.5
1686
+ expect(fsr['STATUS_CODE']).to eql 'NOT_PERFORMED'
1687
+ expect(fsr['FIRST_FILE_NAME']).to eql 'another'
1688
+ end
1689
+ if ENV['MINIO'] && type == 'bucket'
1690
+ file = Tempfile.new
1691
+ OpenC3::Bucket.getClient().get_object(bucket: @bucket, key: File.join(@root_path, 'first.txt'), path: file.path)
1692
+ expect(file.read).to eql 'FIRSTSECOND'
1693
+ file.unlink
1694
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'first.txt'))
1695
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'second.txt'))
1696
+ else
1697
+ expect(File.read(File.join(SPEC_DIR, 'first.txt'))).to eql 'FIRSTSECOND'
1698
+ FileUtils.rm File.join(SPEC_DIR, 'first.txt')
1699
+ FileUtils.rm File.join(SPEC_DIR, 'second.txt')
1700
+ end
1701
+ end
1702
+
1703
+ it "append file error" do
1704
+ if ENV['MINIO'] && type == 'bucket'
1705
+ OpenC3::Bucket.getClient().put_object(bucket: @bucket, key: File.join(@root_path, 'first.txt'), body: 'FIRST')
1706
+ else
1707
+ File.write(File.join(SPEC_DIR, 'first.txt'), 'FIRST')
1708
+ end
1709
+ request(requests: [
1710
+ ['APPEND_FILE', 'first.txt', 'second.txt'],
1711
+ ]) do |indications|
1712
+ indication = indications[-1]
1713
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1714
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1715
+ expect(indication['file_status']).to eql 'UNREPORTED'
1716
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1717
+ expect(indication['status_report']).to eql 'FINISHED'
1718
+ fsr = indication['filestore_responses'][0]
1719
+ expect(fsr['ACTION_CODE']).to eql 'APPEND_FILE'
1720
+ expect(fsr['STATUS_CODE']).to eql 'FILE_2_DOES_NOT_EXIST'
1721
+ expect(fsr['FIRST_FILE_NAME']).to eql 'first.txt'
1722
+ expect(fsr['SECOND_FILE_NAME']).to eql 'second.txt'
1723
+ end
1724
+ if ENV['MINIO'] && type == 'bucket'
1725
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'first.txt'))
1726
+ else
1727
+ FileUtils.rm File.join(SPEC_DIR, 'first.txt')
1728
+ end
1729
+ end
1730
+
1731
+ it "replace file" do
1732
+ if ENV['MINIO'] && type == 'bucket'
1733
+ OpenC3::Bucket.getClient().put_object(bucket: @bucket, key: File.join(@root_path, 'orig.txt'), body: 'ORIG')
1734
+ OpenC3::Bucket.getClient().put_object(bucket: @bucket, key: File.join(@root_path, 'replace.txt'), body: 'REPLACE')
1735
+ else
1736
+ File.write(File.join(SPEC_DIR, 'orig.txt'), 'ORIG')
1737
+ File.write(File.join(SPEC_DIR, 'replace.txt'), 'REPLACE')
1738
+ end
1739
+ request(requests: [
1740
+ ['REPLACE_FILE', 'orig.txt', 'replace.txt'],
1741
+ ['REPLACE_FILE', 'nope', 'replace.txt'],
1742
+ ['REPLACE_FILE', 'another'],
1743
+ ]) do |indications|
1744
+ indication = indications[-1]
1745
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1746
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1747
+ expect(indication['file_status']).to eql 'UNREPORTED'
1748
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1749
+ expect(indication['status_report']).to eql 'FINISHED'
1750
+ fsr = indication['filestore_responses'][0]
1751
+ expect(fsr['ACTION_CODE']).to eql 'REPLACE_FILE'
1752
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1753
+ expect(fsr['FIRST_FILE_NAME']).to eql 'orig.txt'
1754
+ expect(fsr['SECOND_FILE_NAME']).to eql 'replace.txt'
1755
+ fsr = indication['filestore_responses'][1]
1756
+ expect(fsr['ACTION_CODE']).to eql 'REPLACE_FILE'
1757
+ expect(fsr['STATUS_CODE']).to eql 'FILE_1_DOES_NOT_EXIST'
1758
+ expect(fsr['FIRST_FILE_NAME']).to eql 'nope'
1759
+ expect(fsr['SECOND_FILE_NAME']).to eql 'replace.txt'
1760
+ fsr = indication['filestore_responses'][2]
1761
+ expect(fsr['ACTION_CODE']).to eql 'REPLACE_FILE'
1762
+ # Once there is a failure no more are performed per 4.9.5
1763
+ expect(fsr['STATUS_CODE']).to eql 'NOT_PERFORMED'
1764
+ expect(fsr['FIRST_FILE_NAME']).to eql 'another'
1765
+ end
1766
+ if ENV['MINIO'] && type == 'bucket'
1767
+ file = Tempfile.new
1768
+ OpenC3::Bucket.getClient().get_object(bucket: @bucket, key: File.join(@root_path, 'orig.txt'), path: file.path)
1769
+ expect(file.read).to eql 'REPLACE'
1770
+ file.rewind
1771
+ OpenC3::Bucket.getClient().get_object(bucket: @bucket, key: File.join(@root_path, 'replace.txt'), path: file.path)
1772
+ expect(file.read).to eql 'REPLACE'
1773
+ file.unlink
1774
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'orig.txt'))
1775
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'replace.txt'))
1776
+ else
1777
+ expect(File.read(File.join(SPEC_DIR, 'orig.txt'))).to eql 'REPLACE'
1778
+ expect(File.read(File.join(SPEC_DIR, 'replace.txt'))).to eql 'REPLACE' # Still exists
1779
+ FileUtils.rm File.join(SPEC_DIR, 'orig.txt')
1780
+ FileUtils.rm File.join(SPEC_DIR, 'replace.txt')
1781
+ end
1782
+ end
1783
+
1784
+ it "replace file error" do
1785
+ if ENV['MINIO'] && type == 'bucket'
1786
+ OpenC3::Bucket.getClient().put_object(bucket: @bucket, key: File.join(@root_path, 'orig.txt'), body: 'ORIG')
1787
+ else
1788
+ File.write(File.join(SPEC_DIR, 'orig.txt'), 'ORIG')
1789
+ end
1790
+ request(requests: [
1791
+ ['REPLACE_FILE', 'orig.txt', 'replace.txt'],
1792
+ ]) do |indications|
1793
+ indication = indications[-1]
1794
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1795
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1796
+ expect(indication['file_status']).to eql 'UNREPORTED'
1797
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1798
+ expect(indication['status_report']).to eql 'FINISHED'
1799
+ fsr = indication['filestore_responses'][0]
1800
+ expect(fsr['ACTION_CODE']).to eql 'REPLACE_FILE'
1801
+ expect(fsr['STATUS_CODE']).to eql 'FILE_2_DOES_NOT_EXIST'
1802
+ expect(fsr['FIRST_FILE_NAME']).to eql 'orig.txt'
1803
+ expect(fsr['SECOND_FILE_NAME']).to eql 'replace.txt'
1804
+ end
1805
+ if ENV['MINIO'] && type == 'bucket'
1806
+ OpenC3::Bucket.getClient().delete_object(bucket: @bucket, key: File.join(@root_path, 'orig.txt'))
1807
+ else
1808
+ FileUtils.rm File.join(SPEC_DIR, 'orig.txt')
1809
+ end
1810
+ end
1811
+
1812
+ it "create directory" do
1813
+ request(requests: [
1814
+ ['CREATE_DIRECTORY', 'new_dir'],
1815
+ ['CREATE_DIRECTORY', 'new_dir'],
1816
+ ['CREATE_DIRECTORY', 'another_dir'],
1817
+ ]) do |indications|
1818
+ indication = indications[-1]
1819
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1820
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1821
+ expect(indication['file_status']).to eql 'UNREPORTED'
1822
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1823
+ expect(indication['status_report']).to eql 'FINISHED'
1824
+ fsr = indication['filestore_responses'][0]
1825
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_DIRECTORY'
1826
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1827
+ expect(fsr['FIRST_FILE_NAME']).to eql 'new_dir'
1828
+ fsr = indication['filestore_responses'][1]
1829
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_DIRECTORY'
1830
+ if type == 'bucket'
1831
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1832
+ else
1833
+ expect(fsr['STATUS_CODE']).to eql 'CANNOT_BE_CREATED' # already exists
1834
+ end
1835
+ expect(fsr['FIRST_FILE_NAME']).to eql 'new_dir'
1836
+ fsr = indication['filestore_responses'][2]
1837
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_DIRECTORY'
1838
+ if type == 'bucket'
1839
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1840
+ else
1841
+ # Once there is a failure no more are performed per 4.9.5
1842
+ expect(fsr['STATUS_CODE']).to eql 'NOT_PERFORMED'
1843
+ end
1844
+ expect(fsr['FIRST_FILE_NAME']).to eql 'another_dir'
1845
+ end
1846
+ if type != 'bucket'
1847
+ expect(File.directory?(File.join(SPEC_DIR, 'new_dir'))).to be true
1848
+ FileUtils.rmdir File.join(SPEC_DIR, 'new_dir')
1849
+ end
1850
+ end
1851
+
1852
+ it "remove directory" do
1853
+ request(requests: [
1854
+ ['CREATE_DIRECTORY', 'rm_dir'],
1855
+ ['REMOVE_DIRECTORY', 'rm_dir'],
1856
+ ['REMOVE_DIRECTORY', 'rm_dir'], # No longer exists
1857
+ ['REMOVE_DIRECTORY', 'another_dir'],
1858
+ ]) do |indications|
1859
+ indication = indications[-1]
1860
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1861
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1862
+ expect(indication['file_status']).to eql 'UNREPORTED'
1863
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1864
+ expect(indication['status_report']).to eql 'FINISHED'
1865
+ fsr = indication['filestore_responses'][0]
1866
+ expect(fsr['ACTION_CODE']).to eql 'CREATE_DIRECTORY'
1867
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1868
+ expect(fsr['FIRST_FILE_NAME']).to eql 'rm_dir'
1869
+ fsr = indication['filestore_responses'][1]
1870
+ expect(fsr['ACTION_CODE']).to eql 'REMOVE_DIRECTORY'
1871
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1872
+ expect(fsr['FIRST_FILE_NAME']).to eql 'rm_dir'
1873
+ fsr = indication['filestore_responses'][2]
1874
+ expect(fsr['ACTION_CODE']).to eql 'REMOVE_DIRECTORY'
1875
+ if type == 'bucket'
1876
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1877
+ else
1878
+ expect(fsr['STATUS_CODE']).to eql 'DOES_NOT_EXIST'
1879
+ end
1880
+ expect(fsr['FIRST_FILE_NAME']).to eql 'rm_dir'
1881
+ fsr = indication['filestore_responses'][3]
1882
+ expect(fsr['ACTION_CODE']).to eql 'REMOVE_DIRECTORY'
1883
+ if type == 'bucket'
1884
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1885
+ else
1886
+ # Once there is a failure no more are performed per 4.9.5
1887
+ expect(fsr['STATUS_CODE']).to eql 'NOT_PERFORMED'
1888
+ end
1889
+ expect(fsr['FIRST_FILE_NAME']).to eql 'another_dir'
1890
+ end
1891
+ end
1892
+
1893
+ it "deny file" do
1894
+ if ENV['MINIO'] && type == 'bucket'
1895
+ OpenC3::Bucket.getClient().put_object(bucket: @bucket, key: File.join(@root_path, 'deny.txt'), body: 'DENY')
1896
+ else
1897
+ File.write(File.join(SPEC_DIR, 'deny.txt'), 'DENY')
1898
+ end
1899
+ request(requests: [
1900
+ ['DENY_FILE', 'nope'],
1901
+ ['DENY_FILE', 'deny.txt'],
1902
+ ]) do |indications|
1903
+ indication = indications[-1]
1904
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1905
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1906
+ expect(indication['file_status']).to eql 'UNREPORTED'
1907
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1908
+ expect(indication['status_report']).to eql 'FINISHED'
1909
+ fsr = indication['filestore_responses'][0]
1910
+ expect(fsr['ACTION_CODE']).to eql 'DENY_FILE'
1911
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1912
+ expect(fsr['FIRST_FILE_NAME']).to eql 'nope'
1913
+ fsr = indication['filestore_responses'][1]
1914
+ expect(fsr['ACTION_CODE']).to eql 'DENY_FILE'
1915
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1916
+ expect(fsr['FIRST_FILE_NAME']).to eql 'deny.txt'
1917
+ end
1918
+ if ENV['MINIO'] && type == 'bucket'
1919
+ expect(OpenC3::Bucket.getClient().check_object(bucket: @bucket, key: File.join(@root_path, 'deny.txt'))).to be false
1920
+ else
1921
+ expect(File.exist?(File.join(SPEC_DIR, 'deny.txt'))).to be false
1922
+ end
1923
+ end
1924
+
1925
+ it "deny directory" do
1926
+ FileUtils.mkdir(File.join(SPEC_DIR, 'deny_dir'))
1927
+ FileUtils.mkdir(File.join(SPEC_DIR, 'another_dir'))
1928
+ File.write(File.join(SPEC_DIR, 'another_dir', 'file.txt'), 'BLAH')
1929
+ request(requests: [
1930
+ ['DENY_DIRECTORY', 'nope'],
1931
+ ['DENY_DIRECTORY', 'deny_dir'],
1932
+ ['DENY_DIRECTORY', 'another_dir'],
1933
+ ]) do |indications|
1934
+ indication = indications[-1]
1935
+ expect(indication['indication_type']).to eql 'Transaction-Finished'
1936
+ expect(indication['condition_code']).to eql 'NO_ERROR'
1937
+ expect(indication['file_status']).to eql 'UNREPORTED'
1938
+ expect(indication['delivery_code']).to eql 'DATA_COMPLETE'
1939
+ expect(indication['status_report']).to eql 'FINISHED'
1940
+ fsr = indication['filestore_responses'][0]
1941
+ expect(fsr['ACTION_CODE']).to eql 'DENY_DIRECTORY'
1942
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1943
+ expect(fsr['FIRST_FILE_NAME']).to eql 'nope'
1944
+ fsr = indication['filestore_responses'][1]
1945
+ expect(fsr['ACTION_CODE']).to eql 'DENY_DIRECTORY'
1946
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1947
+ expect(fsr['FIRST_FILE_NAME']).to eql 'deny_dir'
1948
+ fsr = indication['filestore_responses'][2]
1949
+ expect(fsr['ACTION_CODE']).to eql 'DENY_DIRECTORY'
1950
+ expect(fsr['FIRST_FILE_NAME']).to eql 'another_dir'
1951
+ if type == 'bucket'
1952
+ expect(fsr['STATUS_CODE']).to eql 'SUCCESSFUL'
1953
+ else
1954
+ expect(fsr['STATUS_CODE']).to eql 'NOT_ALLOWED'
1955
+ expect(fsr['FILESTORE_MESSAGE']).to include("not empty")
1956
+ end
1957
+ end
1958
+ FileUtils.rm_rf(File.join(SPEC_DIR, 'deny_dir')) if type == 'bucket'
1959
+ FileUtils.rm_rf(File.join(SPEC_DIR, 'another_dir'))
1960
+ end
1961
+ end
1962
+ end
1963
+ end
1964
+ end
1965
+ end