openc3-cosmos-cfdp 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +18 -0
- data/README.md +181 -0
- data/Rakefile +40 -0
- data/lib/cfdp.rb +283 -0
- data/lib/cfdp_api.rb +204 -0
- data/microservices/CFDP/Gemfile +37 -0
- data/microservices/CFDP/Rakefile +6 -0
- data/microservices/CFDP/app/controllers/application_controller.rb +46 -0
- data/microservices/CFDP/app/controllers/cfdp_controller.rb +222 -0
- data/microservices/CFDP/app/models/cfdp_checksum.rb +52 -0
- data/microservices/CFDP/app/models/cfdp_crc_checksum.rb +41 -0
- data/microservices/CFDP/app/models/cfdp_mib.rb +613 -0
- data/microservices/CFDP/app/models/cfdp_model.rb +25 -0
- data/microservices/CFDP/app/models/cfdp_null_checksum.rb +29 -0
- data/microservices/CFDP/app/models/cfdp_pdu.rb +202 -0
- data/microservices/CFDP/app/models/cfdp_receive_transaction.rb +590 -0
- data/microservices/CFDP/app/models/cfdp_source_transaction.rb +449 -0
- data/microservices/CFDP/app/models/cfdp_topic.rb +58 -0
- data/microservices/CFDP/app/models/cfdp_transaction.rb +188 -0
- data/microservices/CFDP/app/models/cfdp_user.rb +601 -0
- data/microservices/CFDP/bin/rails +4 -0
- data/microservices/CFDP/bin/rake +4 -0
- data/microservices/CFDP/bin/setup +25 -0
- data/microservices/CFDP/config/application.rb +55 -0
- data/microservices/CFDP/config/boot.rb +4 -0
- data/microservices/CFDP/config/credentials.yml.enc +1 -0
- data/microservices/CFDP/config/environment.rb +5 -0
- data/microservices/CFDP/config/environments/development.rb +53 -0
- data/microservices/CFDP/config/environments/production.rb +75 -0
- data/microservices/CFDP/config/environments/test.rb +50 -0
- data/microservices/CFDP/config/initializers/application_controller_renderer.rb +8 -0
- data/microservices/CFDP/config/initializers/backtrace_silencers.rb +7 -0
- data/microservices/CFDP/config/initializers/cfdp_initializer.rb +26 -0
- data/microservices/CFDP/config/initializers/cors.rb +16 -0
- data/microservices/CFDP/config/initializers/filter_parameter_logging.rb +8 -0
- data/microservices/CFDP/config/initializers/inflections.rb +16 -0
- data/microservices/CFDP/config/initializers/mime_types.rb +4 -0
- data/microservices/CFDP/config/initializers/wrap_parameters.rb +9 -0
- data/microservices/CFDP/config/locales/en.yml +29 -0
- data/microservices/CFDP/config/puma.rb +38 -0
- data/microservices/CFDP/config/routes.rb +16 -0
- data/microservices/CFDP/config.ru +5 -0
- data/microservices/CFDP/init.sh +9 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_ack.rb +82 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_enum.rb +237 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_eof.rb +87 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_file_data.rb +98 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_finished.rb +114 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_keep_alive.rb +65 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_metadata.rb +116 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_nak.rb +91 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_prompt.rb +60 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_tlv.rb +291 -0
- data/microservices/CFDP/lib/cfdp_pdu/cfdp_pdu_user_ops.rb +749 -0
- data/microservices/CFDP/public/robots.txt +1 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_ack_spec.rb +114 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_eof_spec.rb +159 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_file_data_spec.rb +76 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_finished_spec.rb +192 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_keep_alive_spec.rb +69 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_metadata_spec.rb +346 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_nak_spec.rb +126 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_prompt_spec.rb +94 -0
- data/microservices/CFDP/spec/models/cfdp_pdu_spec.rb +111 -0
- data/microservices/CFDP/spec/rails_helper.rb +71 -0
- data/microservices/CFDP/spec/requests/cfdp_spec.rb +1965 -0
- data/microservices/CFDP/spec/spec_helper.rb +200 -0
- data/plugin.txt +67 -0
- data/targets/CFDPTEST/cmd_tlm/cmd.txt +5 -0
- data/targets/CFDPTEST/cmd_tlm/tlm.txt +4 -0
- data/targets/CFDPTEST/procedures/cfdp_test_suite.rb +130 -0
- data/targets/CFDPTEST/target.txt +4 -0
- metadata +118 -0
@@ -0,0 +1,590 @@
|
|
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_relative 'cfdp_transaction'
|
18
|
+
|
19
|
+
class CfdpReceiveTransaction < CfdpTransaction
|
20
|
+
def initialize(pdu_hash)
|
21
|
+
super()
|
22
|
+
@id = CfdpTransaction.build_transaction_id(pdu_hash["SOURCE_ENTITY_ID"], pdu_hash["SEQUENCE_NUMBER"])
|
23
|
+
@transaction_seq_num = pdu_hash["SEQUENCE_NUMBER"]
|
24
|
+
@transmission_mode = pdu_hash["TRANSMISSION_MODE"]
|
25
|
+
@messages_to_user = []
|
26
|
+
@filestore_requests = []
|
27
|
+
@tmp_file = nil
|
28
|
+
@segments = {}
|
29
|
+
@eof_pdu_hash = nil
|
30
|
+
@checksum = CfdpNullChecksum.new
|
31
|
+
@full_checksum_needed = false
|
32
|
+
@file_size = 0
|
33
|
+
@file_status = "UNREPORTED"
|
34
|
+
@delivery_code = "DATA_COMPLETE"
|
35
|
+
@filestore_responses = []
|
36
|
+
@nak_timeout = nil
|
37
|
+
@nak_timeout_count = 0
|
38
|
+
@check_timeout = nil
|
39
|
+
@check_timeout_count = 0
|
40
|
+
@progress = 0
|
41
|
+
@nak_start_of_scope = 0
|
42
|
+
@keep_alive_count = 0
|
43
|
+
@finished_count = 0
|
44
|
+
@source_entity_id = nil
|
45
|
+
@inactivity_timeout = nil
|
46
|
+
@inactivity_count = 0
|
47
|
+
@keep_alive_timeout = nil
|
48
|
+
CfdpMib.transactions[@id] = self
|
49
|
+
handle_pdu(pdu_hash)
|
50
|
+
@inactivity_timeout = Time.now + CfdpMib.entity(@source_entity_id)['keep_alive_interval']
|
51
|
+
@keep_alive_timeout = Time.now + CfdpMib.entity(@source_entity_id)['keep_alive_interval'] if @transmission_mode == 'ACKNOWLEDGED' and CfdpMib.entity(@source_entity_id)['enable_keep_alive']
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_complete
|
55
|
+
return false unless @metadata_pdu_hash and @eof_pdu_hash
|
56
|
+
if @eof_pdu_hash["CONDITION_CODE"] != "NO_ERROR" # Canceled
|
57
|
+
@state = "CANCELED"
|
58
|
+
@transaction_status = "TERMINATED"
|
59
|
+
@condition_code = @eof_pdu_hash["CONDITION_CODE"]
|
60
|
+
@file_status = "FILE_DISCARDED"
|
61
|
+
@delivery_code = "DATA_INCOMPLETE"
|
62
|
+
if CfdpMib.entity(@source_entity_id)['incomplete_file_disposition'] == "DISCARD"
|
63
|
+
@tmp_file.unlink if @tmp_file
|
64
|
+
@tmp_file = nil
|
65
|
+
else
|
66
|
+
# Keep
|
67
|
+
if @tmp_file
|
68
|
+
@tmp_file.close
|
69
|
+
success = CfdpMib.put_destination_file(@destination_file_name, @tmp_file) # Unlink handled by CfdpMib
|
70
|
+
if success
|
71
|
+
@file_status = "FILESTORE_SUCCESS"
|
72
|
+
else
|
73
|
+
@file_status = "FILESTORE_REJECTION"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
notice_of_completion()
|
78
|
+
return true
|
79
|
+
end
|
80
|
+
|
81
|
+
if @source_file_name and @destination_file_name
|
82
|
+
if complete_file_received?
|
83
|
+
@tmp_file ||= Tempfile.new('cfdp')
|
84
|
+
|
85
|
+
# Complete
|
86
|
+
if @checksum.check(@tmp_file, @eof_pdu_hash['FILE_CHECKSUM'], @full_checksum_needed)
|
87
|
+
# Move file to final destination
|
88
|
+
@tmp_file.close
|
89
|
+
success = CfdpMib.put_destination_file(@destination_file_name, @tmp_file) # Unlink handled by CfdpMib
|
90
|
+
if success
|
91
|
+
@file_status = "FILESTORE_SUCCESS"
|
92
|
+
else
|
93
|
+
@file_status = "FILESTORE_REJECTION"
|
94
|
+
@condition_code = "FILESTORE_REJECTION"
|
95
|
+
handle_fault()
|
96
|
+
end
|
97
|
+
@delivery_code = "DATA_COMPLETE"
|
98
|
+
else
|
99
|
+
@tmp_file.unlink
|
100
|
+
@file_status = "FILE_DISCARDED"
|
101
|
+
@condition_code = "FILE_CHECKSUM_FAILURE"
|
102
|
+
handle_fault()
|
103
|
+
@delivery_code = "DATA_INCOMPLETE"
|
104
|
+
end
|
105
|
+
@tmp_file = nil
|
106
|
+
else
|
107
|
+
# Still waiting on file data
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Handle Filestore Requests
|
113
|
+
filestore_success = true
|
114
|
+
tlvs = @metadata_pdu_hash["TLVS"]
|
115
|
+
if tlvs and (@condition_code == "NO_ERROR" or @condition_code == "UNSUPPORTED_CHECKSUM_TYPE")
|
116
|
+
tlvs.each do |tlv|
|
117
|
+
case tlv['TYPE']
|
118
|
+
when 'FILESTORE_REQUEST'
|
119
|
+
action_code = tlv["ACTION_CODE"]
|
120
|
+
first_file_name = tlv["FIRST_FILE_NAME"]
|
121
|
+
second_file_name = tlv["SECOND_FILE_NAME"]
|
122
|
+
if filestore_success
|
123
|
+
status_code, filestore_message = CfdpMib.filestore_request(action_code, first_file_name, second_file_name)
|
124
|
+
filestore_response = {}
|
125
|
+
filestore_response['ACTION_CODE'] = action_code
|
126
|
+
filestore_response['STATUS_CODE'] = status_code
|
127
|
+
filestore_response['FIRST_FILE_NAME'] = first_file_name
|
128
|
+
filestore_response['SECOND_FILE_NAME'] = second_file_name
|
129
|
+
filestore_response['FILESTORE_MESSAGE'] = filestore_message
|
130
|
+
@filestore_responses << filestore_response
|
131
|
+
filestore_success = false if status_code != 'SUCCESSFUL'
|
132
|
+
else
|
133
|
+
filestore_response = {}
|
134
|
+
filestore_response['ACTION_CODE'] = action_code
|
135
|
+
filestore_response['STATUS_CODE'] = "NOT_PERFORMED"
|
136
|
+
filestore_response['FIRST_FILE_NAME'] = first_file_name
|
137
|
+
filestore_response['SECOND_FILE_NAME'] = second_file_name
|
138
|
+
@filestore_responses << filestore_response
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
notice_of_completion()
|
145
|
+
return true
|
146
|
+
end
|
147
|
+
|
148
|
+
def notice_of_completion
|
149
|
+
# Cancel all timeouts
|
150
|
+
@check_timeout = nil
|
151
|
+
@nak_timeout = nil
|
152
|
+
@keep_alive_timeout = nil
|
153
|
+
@inactivity_timeout = nil
|
154
|
+
@finished_ack_timeout = nil
|
155
|
+
|
156
|
+
destination_entity = CfdpMib.source_entity
|
157
|
+
source_entity = CfdpMib.entity(@source_entity_id)
|
158
|
+
if source_entity['enable_finished'] and (@metadata_pdu_hash["CLOSURE_REQUESTED"] == "CLOSURE_REQUESTED" or @transmission_mode == "ACKNOWLEDGED")
|
159
|
+
begin
|
160
|
+
# Lookup outgoing PDU command
|
161
|
+
raise "Unknown source entity: #{@metadata_pdu_hash['SOURCE_ENTITY_ID']}" unless source_entity
|
162
|
+
target_name, packet_name, item_name = source_entity["cmd_info"]
|
163
|
+
raise "cmd_info not defined for source entity: #{@metadata_pdu_hash['SOURCE_ENTITY_ID']}" unless target_name and packet_name and item_name
|
164
|
+
@finished_pdu = CfdpPdu.build_finished_pdu(
|
165
|
+
source_entity: source_entity,
|
166
|
+
transaction_seq_num: @transaction_seq_num,
|
167
|
+
destination_entity: destination_entity,
|
168
|
+
condition_code: @condition_code,
|
169
|
+
segmentation_control: "NOT_PRESERVED",
|
170
|
+
transmission_mode: @transmission_mode,
|
171
|
+
delivery_code: @delivery_code,
|
172
|
+
file_status: @file_status,
|
173
|
+
filestore_responses: @filestore_responses,
|
174
|
+
fault_location_entity_id: nil)
|
175
|
+
cmd_params = {}
|
176
|
+
cmd_params[item_name] = @finished_pdu
|
177
|
+
cmd(target_name, packet_name, cmd_params, scope: ENV['OPENC3_SCOPE'])
|
178
|
+
rescue => err
|
179
|
+
abandon() if @state == "CANCELED"
|
180
|
+
raise err
|
181
|
+
end
|
182
|
+
@finished_ack_timeout = Time.now + source_entity['ack_timer_interval'] if @transmission_mode == "ACKNOWLEDGED" and source_entity['enable_acks']
|
183
|
+
end
|
184
|
+
|
185
|
+
@state = "FINISHED" unless @state == "CANCELED" or @state == "ABANDONED"
|
186
|
+
@transaction_status = "TERMINATED"
|
187
|
+
OpenC3::Logger.info("CFDP Finished Receive Transaction #{@id}, #{@condition_code}", scope: ENV['OPENC3_SCOPE'])
|
188
|
+
|
189
|
+
if CfdpMib.source_entity['transaction_finished_indication']
|
190
|
+
if @filestore_responses.length > 0
|
191
|
+
CfdpTopic.write_indication("Transaction-Finished", transaction_id: @id, condition_code: @condition_code, file_status: @file_status, delivery_code: @delivery_code, status_report: @state, filestore_responses: @filestore_responses)
|
192
|
+
else
|
193
|
+
CfdpTopic.write_indication("Transaction-Finished", transaction_id: @id, condition_code: @condition_code, file_status: @file_status, status_report: @state, delivery_code: @delivery_code)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def complete_file_received?
|
199
|
+
return false unless @file_size
|
200
|
+
offset = 0
|
201
|
+
while offset
|
202
|
+
next_offset = @segments[offset]
|
203
|
+
if next_offset
|
204
|
+
return true if next_offset == @file_size
|
205
|
+
else
|
206
|
+
# See if any segments cover the next offset
|
207
|
+
@segments.each do |segment_offset, segment_next_offset|
|
208
|
+
if offset > segment_offset and offset < segment_next_offset
|
209
|
+
# Found
|
210
|
+
next_offset = segment_next_offset
|
211
|
+
return true if next_offset == @file_size
|
212
|
+
break
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
offset = next_offset
|
217
|
+
end
|
218
|
+
return false
|
219
|
+
end
|
220
|
+
|
221
|
+
def cancel(canceling_entity_id = nil)
|
222
|
+
super(canceling_entity_id)
|
223
|
+
notice_of_completion()
|
224
|
+
end
|
225
|
+
|
226
|
+
def suspend
|
227
|
+
if @transmission_mode == "ACKNOWLEDGED"
|
228
|
+
super()
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def update
|
233
|
+
if @state != "SUSPENDED"
|
234
|
+
if @check_timeout
|
235
|
+
if Time.now > @check_timeout
|
236
|
+
@check_timeout_count += 1
|
237
|
+
if @check_timeout_count < CfdpMib.entity(@source_entity_id)['check_limit']
|
238
|
+
@check_timeout = Time.now + CfdpMib.entity(@source_entity_id)['check_interval']
|
239
|
+
else
|
240
|
+
@condition_code = "CHECK_LIMIT_REACHED"
|
241
|
+
handle_fault()
|
242
|
+
@check_timeout = nil
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
if @nak_timeout
|
247
|
+
if Time.now > @nak_timeout
|
248
|
+
if complete_file_received?
|
249
|
+
@nak_timeout = nil
|
250
|
+
else
|
251
|
+
send_naks(true)
|
252
|
+
@nak_timeout_count += 1
|
253
|
+
if @nak_timeout_count < CfdpMib.entity(@source_entity_id)['nak_timer_expiration_limit']
|
254
|
+
@nak_timeout = Time.now + CfdpMib.entity(@source_entity_id)['nak_timer_interval']
|
255
|
+
else
|
256
|
+
@condition_code = "NAK_LIMIT_REACHED"
|
257
|
+
handle_fault()
|
258
|
+
@nak_timeout = nil
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
if @keep_alive_timeout
|
264
|
+
if @eof_pdu_hash
|
265
|
+
@keep_alive_timeout = nil
|
266
|
+
else
|
267
|
+
if Time.now > @keep_alive_timeout
|
268
|
+
send_keep_alive()
|
269
|
+
@keep_alive_count += 1
|
270
|
+
@keep_alive_timeout = Time.now + CfdpMib.entity(@source_entity_id)['keep_alive_interval']
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
if @inactivity_timeout
|
275
|
+
if @eof_pdu_hash
|
276
|
+
@inactivity_timeout = nil
|
277
|
+
else
|
278
|
+
if Time.now > @inactivity_timeout
|
279
|
+
@inactivity_count += 1
|
280
|
+
if @inactivity_count < CfdpMib.entity(@source_entity_id)['transaction_inactivity_limit']
|
281
|
+
@inactivity_timeout = Time.now + CfdpMib.entity(@source_entity_id)['keep_alive_interval']
|
282
|
+
else
|
283
|
+
@condition_code = "INACTIVITY_DETECTED"
|
284
|
+
handle_fault()
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
if @finished_ack_timeout
|
290
|
+
if @finished_ack_pdu_hash
|
291
|
+
@finished_ack_timeout = nil
|
292
|
+
else
|
293
|
+
if Time.now > @finished_ack_timeout
|
294
|
+
source_entity = CfdpMib.entity(@metadata_pdu_hash['SOURCE_ENTITY_ID'])
|
295
|
+
raise "Unknown source entity: #{@metadata_pdu_hash['SOURCE_ENTITY_ID']}" unless source_entity
|
296
|
+
target_name, packet_name, item_name = source_entity["cmd_info"]
|
297
|
+
raise "cmd_info not defined for source entity: #{@metadata_pdu_hash['SOURCE_ENTITY_ID']}" unless target_name and packet_name and item_name
|
298
|
+
cmd_params = {}
|
299
|
+
cmd_params[item_name] = @finished_pdu
|
300
|
+
cmd(target_name, packet_name, cmd_params, scope: ENV['OPENC3_SCOPE'])
|
301
|
+
@finished_count += 1
|
302
|
+
if @finished_count > CfdpMib.entity(@source_entity_id)['ack_timer_expiration_limit']
|
303
|
+
# Positive ACK Limit Reached Fault
|
304
|
+
@condition_code = "ACK_LIMIT_REACHED"
|
305
|
+
handle_fault()
|
306
|
+
@finished_ack_timeout = nil
|
307
|
+
else
|
308
|
+
@finished_ack_timeout = Time.now + CfdpMib.entity(@source_entity_id)['ack_timer_interval']
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def send_keep_alive
|
317
|
+
source_entity = CfdpMib.entity(@metadata_pdu_hash['SOURCE_ENTITY_ID'])
|
318
|
+
destination_entity = CfdpMib.source_entity
|
319
|
+
target_name, packet_name, item_name = source_entity["cmd_info"]
|
320
|
+
|
321
|
+
keep_alive_pdu = CfdpPdu.build_keep_alive_pdu(
|
322
|
+
source_entity: source_entity,
|
323
|
+
transaction_seq_num: @transaction_seq_num,
|
324
|
+
destination_entity: destination_entity,
|
325
|
+
file_size: @file_size,
|
326
|
+
segmentation_control: "NOT_PRESERVED",
|
327
|
+
transmission_mode: @transmission_mode,
|
328
|
+
progress: @progress)
|
329
|
+
cmd_params = {}
|
330
|
+
cmd_params[item_name] = keep_alive_pdu
|
331
|
+
cmd(target_name, packet_name, cmd_params, scope: ENV['OPENC3_SCOPE'])
|
332
|
+
end
|
333
|
+
|
334
|
+
def send_naks(force = false)
|
335
|
+
source_entity = CfdpMib.entity(@source_entity_id)
|
336
|
+
destination_entity = CfdpMib.source_entity
|
337
|
+
target_name, packet_name, item_name = source_entity["cmd_info"]
|
338
|
+
|
339
|
+
segment_requests = []
|
340
|
+
segment_requests << [0, 0] unless @metadata_pdu_hash
|
341
|
+
|
342
|
+
# TODO: I don't see the metadata_pdu_hash being used anywhere
|
343
|
+
# past this point. The Metadata holds the file size so how can
|
344
|
+
# we know if we haven't received segments if we never check
|
345
|
+
# the original request size?
|
346
|
+
|
347
|
+
if @eof_pdu_hash
|
348
|
+
final_end_of_scope = @file_size
|
349
|
+
else
|
350
|
+
final_end_of_scope = @progress
|
351
|
+
end
|
352
|
+
|
353
|
+
if force
|
354
|
+
offset = 0
|
355
|
+
else
|
356
|
+
offset = @nak_start_of_scope
|
357
|
+
end
|
358
|
+
sorted_segments = @segments.to_a.sort {|a,b| a[0] <=> b[0]}
|
359
|
+
index = 0
|
360
|
+
sorted_segments.each do |start_offset, end_offset|
|
361
|
+
break if end_offset > offset
|
362
|
+
index += 1
|
363
|
+
end
|
364
|
+
sorted_segments = sorted_segments[index..-1]
|
365
|
+
while (offset < final_end_of_scope) and sorted_segments.length > 0
|
366
|
+
found = false
|
367
|
+
sorted_segments.each do |start_offset, end_offset|
|
368
|
+
if offset >= start_offset and offset < end_offset
|
369
|
+
# Offset found - move to end offset
|
370
|
+
offset = end_offset
|
371
|
+
found = true
|
372
|
+
break
|
373
|
+
end
|
374
|
+
end
|
375
|
+
unless found
|
376
|
+
# Need a segment request up to first sorted segment
|
377
|
+
segment_requests << [offset, sorted_segments[0][0]]
|
378
|
+
offset = sorted_segments[0][1]
|
379
|
+
end
|
380
|
+
sorted_segments = sorted_segments[1..-1]
|
381
|
+
end
|
382
|
+
if offset < final_end_of_scope
|
383
|
+
segment_requests << [offset, final_end_of_scope]
|
384
|
+
end
|
385
|
+
|
386
|
+
# Calculate max number of segments in a single NAK PDU
|
387
|
+
if force
|
388
|
+
start_of_scope = 0
|
389
|
+
else
|
390
|
+
start_of_scope = @nak_start_of_scope
|
391
|
+
end
|
392
|
+
max_segments = (source_entity['maximum_file_segment_length'] / 8) - 2 # Minus 2 handles scope fields
|
393
|
+
while true
|
394
|
+
num_segments = segment_requests.length
|
395
|
+
if num_segments > max_segments
|
396
|
+
num_segments = max_segments
|
397
|
+
end
|
398
|
+
current_segment_requests = segment_requests[0..(num_segments - 1)]
|
399
|
+
if current_segment_requests.length == segment_requests.length
|
400
|
+
if @eof_pdu_hash
|
401
|
+
end_of_scope = @file_size
|
402
|
+
else
|
403
|
+
end_of_scope = @progress
|
404
|
+
end
|
405
|
+
@nak_start_of_scope = end_of_scope
|
406
|
+
else
|
407
|
+
end_of_scope = current_segment_requests[-1][1]
|
408
|
+
end
|
409
|
+
if start_of_scope != end_of_scope
|
410
|
+
nak_pdu = CfdpPdu.build_nak_pdu(
|
411
|
+
source_entity: source_entity,
|
412
|
+
transaction_seq_num: @transaction_seq_num,
|
413
|
+
destination_entity: destination_entity,
|
414
|
+
file_size: @file_size,
|
415
|
+
segmentation_control: "NOT_PRESERVED",
|
416
|
+
transmission_mode: @transmission_mode,
|
417
|
+
start_of_scope: start_of_scope,
|
418
|
+
end_of_scope: end_of_scope,
|
419
|
+
segment_requests: current_segment_requests)
|
420
|
+
segment_requests = segment_requests[num_segments..-1]
|
421
|
+
start_of_scope = end_of_scope
|
422
|
+
cmd_params = {}
|
423
|
+
cmd_params[item_name] = nak_pdu
|
424
|
+
cmd(target_name, packet_name, cmd_params, scope: ENV['OPENC3_SCOPE'])
|
425
|
+
end
|
426
|
+
break if segment_requests.length <= 0
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def handle_pdu(pdu_hash)
|
431
|
+
source_entity = CfdpMib.entity(@source_entity_id)
|
432
|
+
@inactivity_timeout = Time.now + source_entity['keep_alive_interval'] if source_entity
|
433
|
+
|
434
|
+
case pdu_hash["DIRECTIVE_CODE"]
|
435
|
+
when "METADATA"
|
436
|
+
@metadata_pdu_count += 1
|
437
|
+
return if @metadata_pdu_hash # Discard repeats
|
438
|
+
@metadata_pdu_hash = pdu_hash
|
439
|
+
@source_entity_id = @metadata_pdu_hash['SOURCE_ENTITY_ID']
|
440
|
+
kw_args = {}
|
441
|
+
tlvs = pdu_hash['TLVS']
|
442
|
+
if tlvs
|
443
|
+
tlvs.each do |tlv|
|
444
|
+
case tlv["TYPE"]
|
445
|
+
when "FILESTORE_REQUEST"
|
446
|
+
filestore_request = {}
|
447
|
+
filestore_request["ACTION_CODE"] = tlv["ACTION_CODE"]
|
448
|
+
filestore_request["FIRST_FILE_NAME"] = tlv["FIRST_FILE_NAME"]
|
449
|
+
filestore_request["SECOND_FILE_NAME"] = tlv["SECOND_FILE_NAME"] if tlv["SECOND_FILE_NAME"]
|
450
|
+
@filestore_requests << filestore_request
|
451
|
+
|
452
|
+
when "MESSAGE_TO_USER"
|
453
|
+
@messages_to_user << tlv["MESSAGE_TO_USER"]
|
454
|
+
|
455
|
+
when "FAULT_HANDLER_OVERRIDE"
|
456
|
+
@fault_handler_overrides[tlv["CONDITION_CODE"]] = tlv["HANDLER_CODE"]
|
457
|
+
|
458
|
+
when "FLOW_LABEL"
|
459
|
+
kw_args[:flow_label] = tlv["FLOW_LABEL"]
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end
|
463
|
+
kw_args[:filestore_requests] = @filestore_requests unless @filestore_requests.empty?
|
464
|
+
kw_args[:messages_to_user] = @messages_to_user unless @messages_to_user.empty?
|
465
|
+
kw_args[:fault_handler_overrides] = @fault_handler_overrides unless @fault_handler_overrides.empty?
|
466
|
+
|
467
|
+
kw_args[:transaction_id] = @id
|
468
|
+
kw_args[:source_entity_id] = @metadata_pdu_hash['SOURCE_ENTITY_ID']
|
469
|
+
|
470
|
+
@file_size = @metadata_pdu_hash['FILE_SIZE']
|
471
|
+
kw_args[:file_size] = @file_size
|
472
|
+
|
473
|
+
@source_file_name = nil
|
474
|
+
if @metadata_pdu_hash['SOURCE_FILE_NAME'] and @metadata_pdu_hash['SOURCE_FILE_NAME'].length > 0
|
475
|
+
@source_file_name = @metadata_pdu_hash['SOURCE_FILE_NAME']
|
476
|
+
kw_args[:source_file_name] = @source_file_name
|
477
|
+
end
|
478
|
+
|
479
|
+
@destination_file_name = nil
|
480
|
+
if @metadata_pdu_hash['DESTINATION_FILE_NAME'] and @metadata_pdu_hash['DESTINATION_FILE_NAME'].length > 0
|
481
|
+
@destination_file_name = @metadata_pdu_hash['DESTINATION_FILE_NAME']
|
482
|
+
kw_args[:destination_file_name] = @destination_file_name
|
483
|
+
end
|
484
|
+
|
485
|
+
CfdpTopic.write_indication("Metadata-Recv", **kw_args)
|
486
|
+
|
487
|
+
@checksum = get_checksum(@metadata_pdu_hash["CHECKSUM_TYPE"])
|
488
|
+
unless @checksum
|
489
|
+
# Use Null checksum if checksum type not available
|
490
|
+
@condition_code = "UNSUPPORTED_CHECKSUM_TYPE"
|
491
|
+
handle_fault()
|
492
|
+
@checksum = CfdpNullChecksum.new
|
493
|
+
end
|
494
|
+
|
495
|
+
when "EOF"
|
496
|
+
@eof_pdu_hash = pdu_hash
|
497
|
+
|
498
|
+
# Check file size fault
|
499
|
+
@file_size = @eof_pdu_hash["FILE_SIZE"]
|
500
|
+
if @progress > @file_size
|
501
|
+
@condition_code = "FILE_SIZE_ERROR"
|
502
|
+
handle_fault()
|
503
|
+
end
|
504
|
+
|
505
|
+
CfdpTopic.write_indication("EOF-Recv", transaction_id: @id) if CfdpMib.source_entity['eof_recv_indication']
|
506
|
+
|
507
|
+
destination_entity = CfdpMib.source_entity
|
508
|
+
source_entity = CfdpMib.entity(@metadata_pdu_hash['SOURCE_ENTITY_ID'])
|
509
|
+
if @transmission_mode == "ACKNOWLEDGED" and source_entity['enable_acks']
|
510
|
+
target_name, packet_name, item_name = source_entity["cmd_info"]
|
511
|
+
# Ack EOF PDU
|
512
|
+
ack_pdu = CfdpPdu.build_ack_pdu(
|
513
|
+
source_entity: source_entity,
|
514
|
+
transaction_seq_num: @transaction_seq_num,
|
515
|
+
destination_entity: destination_entity,
|
516
|
+
segmentation_control: "NOT_PRESERVED",
|
517
|
+
transmission_mode: @transmission_mode,
|
518
|
+
condition_code: @eof_pdu_hash["CONDITION_CODE"],
|
519
|
+
ack_directive_code: "EOF",
|
520
|
+
transaction_status: "ACTIVE")
|
521
|
+
cmd_params = {}
|
522
|
+
cmd_params[item_name] = ack_pdu
|
523
|
+
cmd(target_name, packet_name, cmd_params, scope: ENV['OPENC3_SCOPE'])
|
524
|
+
end
|
525
|
+
|
526
|
+
# Note: This also handles canceled
|
527
|
+
complete = check_complete()
|
528
|
+
if complete
|
529
|
+
send_naks(true) if destination_entity['enable_eof_nak']
|
530
|
+
else
|
531
|
+
@check_timeout = Time.now + source_entity['check_interval']
|
532
|
+
@progress = @file_size
|
533
|
+
send_naks() if destination_entity['immediate_nak_mode'] or destination_entity['enable_eof_nak']
|
534
|
+
@nak_timeout = Time.now + source_entity['nak_timer_interval']
|
535
|
+
end
|
536
|
+
|
537
|
+
when "NAK", "FINISHED", "KEEP_ALIVE"
|
538
|
+
# Unexpected - Ignore
|
539
|
+
|
540
|
+
when "ACK"
|
541
|
+
@finished_ack_pdu_hash = pdu_hash
|
542
|
+
@finished_ack_timeout = nil
|
543
|
+
|
544
|
+
when "PROMPT"
|
545
|
+
@prompt_pdu_hash = pdu_hash
|
546
|
+
unless @eof_pdu_hash
|
547
|
+
if @prompt_pdu_hash['RESPONSE_REQUIRED'] == 'NAK'
|
548
|
+
send_naks()
|
549
|
+
else
|
550
|
+
send_keep_alive()
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
else # File Data
|
555
|
+
@source_entity_id = @metadata_pdu_hash['SOURCE_ENTITY_ID']
|
556
|
+
|
557
|
+
@tmp_file ||= Tempfile.new('cfdp')
|
558
|
+
offset = pdu_hash['OFFSET']
|
559
|
+
file_data = pdu_hash['FILE_DATA']
|
560
|
+
progress = offset + file_data.length
|
561
|
+
|
562
|
+
need_send_naks = false
|
563
|
+
if @transmission_mode == "ACKNOWLEDGED" and CfdpMib.entity(@source_entity_id)['immediate_nak_mode']
|
564
|
+
need_send_naks = true unless @metadata_pdu_hash
|
565
|
+
need_send_naks = true if offset != @progress and @progress < offset
|
566
|
+
end
|
567
|
+
|
568
|
+
@progress = progress if progress > @progress
|
569
|
+
|
570
|
+
# Ignore repeated segments
|
571
|
+
if !@segments[offset] or @segments[offset] != progress
|
572
|
+
if @file_size and progress > @file_size
|
573
|
+
@condition_code = "FILE_SIZE_ERROR"
|
574
|
+
handle_fault()
|
575
|
+
else
|
576
|
+
@full_checksum_needed = true unless @metadata_pdu_hash
|
577
|
+
@checksum.add(offset, file_data)
|
578
|
+
@segments[offset] = offset + file_data.length
|
579
|
+
@tmp_file.seek(offset, IO::SEEK_SET)
|
580
|
+
@tmp_file.write(file_data)
|
581
|
+
end
|
582
|
+
check_complete()
|
583
|
+
|
584
|
+
CfdpTopic.write_indication("File-Segment-Recv", transaction_id: @id, offset: offset, length: file_data.length) if CfdpMib.source_entity['file_segment_recv_indication']
|
585
|
+
end
|
586
|
+
|
587
|
+
send_naks() if need_send_naks
|
588
|
+
end
|
589
|
+
end
|
590
|
+
end
|