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,346 @@
|
|
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 'cfdp_pdu'
|
19
|
+
require 'openc3/models/microservice_model'
|
20
|
+
require 'openc3/utilities/store_autoload'
|
21
|
+
|
22
|
+
RSpec.describe CfdpPdu, type: :model do
|
23
|
+
before(:each) do
|
24
|
+
mock_redis()
|
25
|
+
@source_entity_id = 1
|
26
|
+
@destination_entity_id = 2
|
27
|
+
ENV['OPENC3_MICROSERVICE_NAME'] = 'DEFAULT__API__CFDP'
|
28
|
+
# Create the model that is consumed by CfdpMib.setup
|
29
|
+
model = OpenC3::MicroserviceModel.new(name: ENV['OPENC3_MICROSERVICE_NAME'], scope: "DEFAULT",
|
30
|
+
options: [
|
31
|
+
["source_entity_id", @source_entity_id],
|
32
|
+
["destination_entity_id", @destination_entity_id],
|
33
|
+
["root_path", SPEC_DIR],
|
34
|
+
],
|
35
|
+
)
|
36
|
+
model.create
|
37
|
+
CfdpMib.setup
|
38
|
+
end
|
39
|
+
|
40
|
+
# Validate Table 5-9: Metadata PDU Contents
|
41
|
+
describe "build_metadata_pdu" do
|
42
|
+
it "builds a Metadata PDU with no options" do
|
43
|
+
buffer = CfdpPdu.build_metadata_pdu(
|
44
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
45
|
+
transaction_seq_num: 1,
|
46
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
47
|
+
file_size: 0xDEADBEEF,
|
48
|
+
segmentation_control: "NOT_PRESERVED",
|
49
|
+
transmission_mode: nil,
|
50
|
+
source_file_name: "filename",
|
51
|
+
destination_file_name: "test",
|
52
|
+
closure_requested: 1,
|
53
|
+
options: [])
|
54
|
+
# puts buffer.formatted
|
55
|
+
expect(buffer.length).to eql 29
|
56
|
+
|
57
|
+
# By default the first 7 bytes are the header
|
58
|
+
# This assumes 1 byte per entity ID and sequence number
|
59
|
+
|
60
|
+
# Directive Code
|
61
|
+
expect(buffer[7].unpack('C')[0]).to eql 7 # Metadata per Table 5-4
|
62
|
+
# Closure requested
|
63
|
+
expect(buffer[8].unpack('C')[0] >> 6).to eql 1
|
64
|
+
# Checksum type
|
65
|
+
expect(buffer[8].unpack('C')[0] & 0xF).to eql 0 # legacy modular checksum
|
66
|
+
# File Size
|
67
|
+
expect(buffer[9..12].unpack('N')[0]).to eql 0xDEADBEEF
|
68
|
+
# Source File Name
|
69
|
+
expect(buffer[13].unpack('C')[0]).to eql 8
|
70
|
+
expect(buffer[14..21].unpack('A*')[0]).to eql 'filename'
|
71
|
+
# Destination File Name
|
72
|
+
expect(buffer[22].unpack('C')[0]).to eql 4
|
73
|
+
expect(buffer[23..26].unpack('A*')[0]).to eql 'test'
|
74
|
+
|
75
|
+
hash = {}
|
76
|
+
# decom takes just the Metadata specific part of the buffer
|
77
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
78
|
+
CfdpPdu.decom_metadata_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
79
|
+
expect(hash['CLOSURE_REQUESTED']).to eql 'CLOSURE_REQUESTED'
|
80
|
+
expect(hash['CHECKSUM_TYPE']).to eql 0
|
81
|
+
expect(hash['FILE_SIZE']).to eql 0xDEADBEEF
|
82
|
+
expect(hash['SOURCE_FILE_NAME']).to eql 'filename'
|
83
|
+
expect(hash['DESTINATION_FILE_NAME']).to eql 'test'
|
84
|
+
end
|
85
|
+
|
86
|
+
it "builds a Metadata PDU with option: filestore request" do
|
87
|
+
buffer = CfdpPdu.build_metadata_pdu(
|
88
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
89
|
+
transaction_seq_num: 1,
|
90
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
91
|
+
file_size: 0xDEADBEEF,
|
92
|
+
segmentation_control: "NOT_PRESERVED",
|
93
|
+
transmission_mode: nil,
|
94
|
+
source_file_name: "filename",
|
95
|
+
destination_file_name: "test",
|
96
|
+
closure_requested: 0,
|
97
|
+
options: [{
|
98
|
+
"TYPE" => "FILESTORE_REQUEST",
|
99
|
+
"ACTION_CODE" => "DELETE_FILE", # 1
|
100
|
+
"FIRST_FILE_NAME" => "first",
|
101
|
+
},
|
102
|
+
{
|
103
|
+
"TYPE" => "FILESTORE_REQUEST",
|
104
|
+
"ACTION_CODE" => "RENAME_FILE", # 2
|
105
|
+
"FIRST_FILE_NAME" => "begin",
|
106
|
+
"SECOND_FILE_NAME" => "end",
|
107
|
+
}])
|
108
|
+
# puts buffer.formatted
|
109
|
+
expect(buffer.length).to eql 51
|
110
|
+
|
111
|
+
# By default the first 7 bytes are the header
|
112
|
+
# This assumes 1 byte per entity ID and sequence number
|
113
|
+
|
114
|
+
# Directive Code
|
115
|
+
expect(buffer[7].unpack('C')[0]).to eql 7 # Metadata per Table 5-4
|
116
|
+
# Closure requested
|
117
|
+
expect(buffer[8].unpack('C')[0] >> 6).to eql 0
|
118
|
+
# Checksum type
|
119
|
+
expect(buffer[8].unpack('C')[0] & 0xF).to eql 0 # legacy modular checksum
|
120
|
+
# File Size
|
121
|
+
expect(buffer[9..12].unpack('N')[0]).to eql 0xDEADBEEF
|
122
|
+
# Source File Name
|
123
|
+
expect(buffer[13].unpack('C')[0]).to eql 8
|
124
|
+
expect(buffer[14..21].unpack('A*')[0]).to eql 'filename'
|
125
|
+
# Destination File Name
|
126
|
+
expect(buffer[22].unpack('C')[0]).to eql 4
|
127
|
+
expect(buffer[23..26].unpack('A*')[0]).to eql 'test'
|
128
|
+
# Option TLV Type
|
129
|
+
expect(buffer[27].unpack('C')[0]).to eql 0 # 5.4.1.1 Filestore Request
|
130
|
+
# Option TLV Length
|
131
|
+
expect(buffer[28].unpack('C')[0]).to eql 7 # Action code + length field + length
|
132
|
+
# Option TLV Value
|
133
|
+
# Action Code
|
134
|
+
expect(buffer[29].unpack('C')[0] >> 4).to eql 1
|
135
|
+
# First filename length
|
136
|
+
expect(buffer[30].unpack('C')[0]).to eql 5
|
137
|
+
# First filename
|
138
|
+
expect(buffer[31..35].unpack('A*')[0]).to eql 'first'
|
139
|
+
# Option TLV Type
|
140
|
+
expect(buffer[36].unpack('C')[0]).to eql 0 # 5.4.1.1 Filestore Request
|
141
|
+
# Option TLV Length
|
142
|
+
expect(buffer[37].unpack('C')[0]).to eql 11
|
143
|
+
# Option TLV Value
|
144
|
+
# Action Code
|
145
|
+
expect(buffer[38].unpack('C')[0] >> 4).to eql 2
|
146
|
+
# First filename length
|
147
|
+
expect(buffer[39].unpack('C')[0]).to eql 5
|
148
|
+
# First filename
|
149
|
+
expect(buffer[40..44].unpack('A*')[0]).to eql 'begin'
|
150
|
+
# Second filename length
|
151
|
+
expect(buffer[45].unpack('C')[0]).to eql 3
|
152
|
+
# Second filename
|
153
|
+
expect(buffer[46..48].unpack('A*')[0]).to eql 'end'
|
154
|
+
|
155
|
+
hash = {}
|
156
|
+
# decom takes just the Metadata specific part of the buffer
|
157
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
158
|
+
CfdpPdu.decom_metadata_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
159
|
+
expect(hash['CLOSURE_REQUESTED']).to eql 'CLOSURE_NOT_REQUESTED'
|
160
|
+
expect(hash['CHECKSUM_TYPE']).to eql 0
|
161
|
+
expect(hash['FILE_SIZE']).to eql 0xDEADBEEF
|
162
|
+
expect(hash['SOURCE_FILE_NAME']).to eql 'filename'
|
163
|
+
expect(hash['DESTINATION_FILE_NAME']).to eql 'test'
|
164
|
+
tlv = hash['TLVS'][0]
|
165
|
+
expect(tlv['TYPE']).to eql 'FILESTORE_REQUEST'
|
166
|
+
expect(tlv['ACTION_CODE']).to eql 'DELETE_FILE'
|
167
|
+
expect(tlv['FIRST_FILE_NAME']).to eql 'first'
|
168
|
+
tlv = hash['TLVS'][1]
|
169
|
+
expect(tlv['TYPE']).to eql 'FILESTORE_REQUEST'
|
170
|
+
expect(tlv['ACTION_CODE']).to eql 'RENAME_FILE'
|
171
|
+
expect(tlv['FIRST_FILE_NAME']).to eql 'begin'
|
172
|
+
expect(tlv['SECOND_FILE_NAME']).to eql 'end'
|
173
|
+
end
|
174
|
+
|
175
|
+
it "builds a Metadata PDU with option: message to user" do
|
176
|
+
buffer = CfdpPdu.build_metadata_pdu(
|
177
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
178
|
+
transaction_seq_num: 1,
|
179
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
180
|
+
file_size: 0xDEADBEEF,
|
181
|
+
segmentation_control: "NOT_PRESERVED",
|
182
|
+
transmission_mode: nil,
|
183
|
+
source_file_name: "filename",
|
184
|
+
destination_file_name: "test",
|
185
|
+
closure_requested: 0,
|
186
|
+
options: [{
|
187
|
+
"TYPE" => "MESSAGE_TO_USER",
|
188
|
+
"MESSAGE_TO_USER" => "Hello"
|
189
|
+
}])
|
190
|
+
# puts buffer.formatted
|
191
|
+
expect(buffer.length).to eql 36
|
192
|
+
|
193
|
+
# By default the first 7 bytes are the header
|
194
|
+
# This assumes 1 byte per entity ID and sequence number
|
195
|
+
|
196
|
+
# Directive Code
|
197
|
+
expect(buffer[7].unpack('C')[0]).to eql 7 # Metadata per Table 5-4
|
198
|
+
# Closure requested
|
199
|
+
expect(buffer[8].unpack('C')[0] >> 6).to eql 0
|
200
|
+
# Checksum type
|
201
|
+
expect(buffer[8].unpack('C')[0] & 0xF).to eql 0 # legacy modular checksum
|
202
|
+
# File Size
|
203
|
+
expect(buffer[9..12].unpack('N')[0]).to eql 0xDEADBEEF
|
204
|
+
# Source File Name
|
205
|
+
expect(buffer[13].unpack('C')[0]).to eql 8
|
206
|
+
expect(buffer[14..21].unpack('A*')[0]).to eql 'filename'
|
207
|
+
# Destination File Name
|
208
|
+
expect(buffer[22].unpack('C')[0]).to eql 4
|
209
|
+
expect(buffer[23..26].unpack('A*')[0]).to eql 'test'
|
210
|
+
# Option TLV Type
|
211
|
+
expect(buffer[27].unpack('C')[0]).to eql 2 # 5.4.3 Message to User
|
212
|
+
# Option TLV Length
|
213
|
+
expect(buffer[28].unpack('C')[0]).to eql 5
|
214
|
+
# Option TLV Value
|
215
|
+
expect(buffer[29..33].unpack('A*')[0]).to eql 'Hello'
|
216
|
+
|
217
|
+
hash = {}
|
218
|
+
# decom takes just the Metadata specific part of the buffer
|
219
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
220
|
+
CfdpPdu.decom_metadata_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
221
|
+
expect(hash['CLOSURE_REQUESTED']).to eql 'CLOSURE_NOT_REQUESTED'
|
222
|
+
expect(hash['CHECKSUM_TYPE']).to eql 0
|
223
|
+
expect(hash['FILE_SIZE']).to eql 0xDEADBEEF
|
224
|
+
expect(hash['SOURCE_FILE_NAME']).to eql 'filename'
|
225
|
+
expect(hash['DESTINATION_FILE_NAME']).to eql 'test'
|
226
|
+
tlv = hash['TLVS'][0]
|
227
|
+
expect(tlv['TYPE']).to eql 'MESSAGE_TO_USER'
|
228
|
+
expect(tlv['MESSAGE_TO_USER']).to eql 'Hello'
|
229
|
+
end
|
230
|
+
|
231
|
+
it "builds a Metadata PDU with option: fault handler override" do
|
232
|
+
buffer = CfdpPdu.build_metadata_pdu(
|
233
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
234
|
+
transaction_seq_num: 1,
|
235
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
236
|
+
file_size: 0xDEADBEEF,
|
237
|
+
segmentation_control: "NOT_PRESERVED",
|
238
|
+
transmission_mode: nil,
|
239
|
+
source_file_name: "filename",
|
240
|
+
destination_file_name: "test",
|
241
|
+
closure_requested: 0,
|
242
|
+
options: [{
|
243
|
+
"TYPE" => "FAULT_HANDLER_OVERRIDE",
|
244
|
+
"CONDITION_CODE" => "INVALID_TRANSMISSION_MODE", # 3
|
245
|
+
"HANDLER_CODE" => "ABONDON_TRANSACTION", # 4
|
246
|
+
}])
|
247
|
+
# puts buffer.formatted
|
248
|
+
expect(buffer.length).to eql 32
|
249
|
+
|
250
|
+
# By default the first 7 bytes are the header
|
251
|
+
# This assumes 1 byte per entity ID and sequence number
|
252
|
+
|
253
|
+
# Directive Code
|
254
|
+
expect(buffer[7].unpack('C')[0]).to eql 7 # Metadata per Table 5-4
|
255
|
+
# Closure requested
|
256
|
+
expect(buffer[8].unpack('C')[0] >> 6).to eql 0
|
257
|
+
# Checksum type
|
258
|
+
expect(buffer[8].unpack('C')[0] & 0xF).to eql 0 # legacy modular checksum
|
259
|
+
# File Size
|
260
|
+
expect(buffer[9..12].unpack('N')[0]).to eql 0xDEADBEEF
|
261
|
+
# Source File Name
|
262
|
+
expect(buffer[13].unpack('C')[0]).to eql 8
|
263
|
+
expect(buffer[14..21].unpack('A*')[0]).to eql 'filename'
|
264
|
+
# Destination File Name
|
265
|
+
expect(buffer[22].unpack('C')[0]).to eql 4
|
266
|
+
expect(buffer[23..26].unpack('A*')[0]).to eql 'test'
|
267
|
+
# Option TLV Type
|
268
|
+
expect(buffer[27].unpack('C')[0]).to eql 4 # 5.4.4 Fault Handler Override
|
269
|
+
# Option TLV Length
|
270
|
+
expect(buffer[28].unpack('C')[0]).to eql 1
|
271
|
+
# Option TLV Value
|
272
|
+
expect(buffer[29].unpack('C')[0] >> 4).to eql 3
|
273
|
+
expect(buffer[29].unpack('C')[0] & 0xF).to eql 4
|
274
|
+
|
275
|
+
hash = {}
|
276
|
+
# decom takes just the Metadata specific part of the buffer
|
277
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
278
|
+
CfdpPdu.decom_metadata_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
279
|
+
expect(hash['CLOSURE_REQUESTED']).to eql 'CLOSURE_NOT_REQUESTED'
|
280
|
+
expect(hash['CHECKSUM_TYPE']).to eql 0
|
281
|
+
expect(hash['FILE_SIZE']).to eql 0xDEADBEEF
|
282
|
+
expect(hash['SOURCE_FILE_NAME']).to eql 'filename'
|
283
|
+
expect(hash['DESTINATION_FILE_NAME']).to eql 'test'
|
284
|
+
tlv = hash['TLVS'][0]
|
285
|
+
expect(tlv['TYPE']).to eql 'FAULT_HANDLER_OVERRIDE'
|
286
|
+
expect(tlv['CONDITION_CODE']).to eql 'INVALID_TRANSMISSION_MODE'
|
287
|
+
expect(tlv['HANDLER_CODE']).to eql 'ABONDON_TRANSACTION'
|
288
|
+
end
|
289
|
+
|
290
|
+
it "builds a Metadata PDU with option: flow label" do
|
291
|
+
buffer = CfdpPdu.build_metadata_pdu(
|
292
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
293
|
+
transaction_seq_num: 1,
|
294
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
295
|
+
file_size: 0xDEADBEEF,
|
296
|
+
segmentation_control: "NOT_PRESERVED",
|
297
|
+
transmission_mode: nil,
|
298
|
+
source_file_name: "filename",
|
299
|
+
destination_file_name: "test",
|
300
|
+
closure_requested: 0,
|
301
|
+
options: [{
|
302
|
+
"TYPE" => "FLOW_LABEL",
|
303
|
+
"FLOW_LABEL" => "flow"
|
304
|
+
}])
|
305
|
+
# puts buffer.formatted
|
306
|
+
expect(buffer.length).to eql 35
|
307
|
+
|
308
|
+
# By default the first 7 bytes are the header
|
309
|
+
# This assumes 1 byte per entity ID and sequence number
|
310
|
+
|
311
|
+
# Directive Code
|
312
|
+
expect(buffer[7].unpack('C')[0]).to eql 7 # Metadata per Table 5-4
|
313
|
+
# Closure requested
|
314
|
+
expect(buffer[8].unpack('C')[0] >> 6).to eql 0
|
315
|
+
# Checksum type
|
316
|
+
expect(buffer[8].unpack('C')[0] & 0xF).to eql 0 # legacy modular checksum
|
317
|
+
# File Size
|
318
|
+
expect(buffer[9..12].unpack('N')[0]).to eql 0xDEADBEEF
|
319
|
+
# Source File Name
|
320
|
+
expect(buffer[13].unpack('C')[0]).to eql 8
|
321
|
+
expect(buffer[14..21].unpack('A*')[0]).to eql 'filename'
|
322
|
+
# Destination File Name
|
323
|
+
expect(buffer[22].unpack('C')[0]).to eql 4
|
324
|
+
expect(buffer[23..26].unpack('A*')[0]).to eql 'test'
|
325
|
+
# Option TLV Type
|
326
|
+
expect(buffer[27].unpack('C')[0]).to eql 5 # 5.4.5 Flow Label
|
327
|
+
# Option TLV Length
|
328
|
+
expect(buffer[28].unpack('C')[0]).to eql 4
|
329
|
+
# Option TLV Value
|
330
|
+
expect(buffer[29..32].unpack('A*')[0]).to eql 'flow'
|
331
|
+
|
332
|
+
hash = {}
|
333
|
+
# decom takes just the Metadata specific part of the buffer
|
334
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
335
|
+
CfdpPdu.decom_metadata_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
336
|
+
expect(hash['CLOSURE_REQUESTED']).to eql 'CLOSURE_NOT_REQUESTED'
|
337
|
+
expect(hash['CHECKSUM_TYPE']).to eql 0
|
338
|
+
expect(hash['FILE_SIZE']).to eql 0xDEADBEEF
|
339
|
+
expect(hash['SOURCE_FILE_NAME']).to eql 'filename'
|
340
|
+
expect(hash['DESTINATION_FILE_NAME']).to eql 'test'
|
341
|
+
tlv = hash['TLVS'][0]
|
342
|
+
expect(tlv['TYPE']).to eql 'FLOW_LABEL'
|
343
|
+
expect(tlv['FLOW_LABEL']).to eql 'flow'
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
@@ -0,0 +1,126 @@
|
|
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 'cfdp_pdu'
|
19
|
+
require 'openc3/models/microservice_model'
|
20
|
+
require 'openc3/utilities/store_autoload'
|
21
|
+
|
22
|
+
RSpec.describe CfdpPdu, type: :model do
|
23
|
+
before(:each) do
|
24
|
+
mock_redis()
|
25
|
+
@source_entity_id = 1
|
26
|
+
@destination_entity_id = 2
|
27
|
+
ENV['OPENC3_MICROSERVICE_NAME'] = 'DEFAULT__API__CFDP'
|
28
|
+
# Create the model that is consumed by CfdpMib.setup
|
29
|
+
model = OpenC3::MicroserviceModel.new(name: ENV['OPENC3_MICROSERVICE_NAME'], scope: "DEFAULT",
|
30
|
+
options: [
|
31
|
+
["source_entity_id", @source_entity_id],
|
32
|
+
["destination_entity_id", @destination_entity_id],
|
33
|
+
["root_path", SPEC_DIR],
|
34
|
+
],
|
35
|
+
)
|
36
|
+
model.create
|
37
|
+
CfdpMib.setup
|
38
|
+
end
|
39
|
+
|
40
|
+
# Validate Table 5-10: NAK PDU Contents
|
41
|
+
describe "build_nak_pdu" do
|
42
|
+
it "builds a NAK PDU with no segement requests" do
|
43
|
+
buffer = CfdpPdu.build_nak_pdu(
|
44
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
45
|
+
transaction_seq_num: 1,
|
46
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
47
|
+
file_size: 0xDEADBEEF,
|
48
|
+
segmentation_control: "NOT_PRESERVED",
|
49
|
+
transmission_mode: nil,
|
50
|
+
start_of_scope: 0x12345678,
|
51
|
+
end_of_scope: 0xABCDEFFF,
|
52
|
+
segment_requests: [])
|
53
|
+
# puts buffer.formatted
|
54
|
+
expect(buffer.length).to eql 18
|
55
|
+
|
56
|
+
# By default the first 7 bytes are the header
|
57
|
+
# This assumes 1 byte per entity ID and sequence number
|
58
|
+
|
59
|
+
# Directive Code
|
60
|
+
expect(buffer[7].unpack('C')[0]).to eql 8 # NAK per Table 5-4
|
61
|
+
# Start of scope
|
62
|
+
expect(buffer[8..11].unpack('N')[0]).to eql 0x12345678
|
63
|
+
# End of scope
|
64
|
+
expect(buffer[12..15].unpack('N')[0]).to eql 0xABCDEFFF
|
65
|
+
|
66
|
+
hash = {}
|
67
|
+
# decom takes just the NAK specific part of the buffer
|
68
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
69
|
+
CfdpPdu.decom_nak_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
70
|
+
expect(hash['START_OF_SCOPE']).to eql 0x12345678
|
71
|
+
expect(hash['END_OF_SCOPE']).to eql 0xABCDEFFF
|
72
|
+
expect(hash['SEGMENT_REQUESTS']).to eql []
|
73
|
+
end
|
74
|
+
|
75
|
+
# Validate Table 5-11: Segment Request Form
|
76
|
+
it "builds a NAK PDU with segement requests" do
|
77
|
+
buffer = CfdpPdu.build_nak_pdu(
|
78
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
79
|
+
transaction_seq_num: 1,
|
80
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
81
|
+
file_size: 0xDEADBEEF,
|
82
|
+
segmentation_control: "NOT_PRESERVED",
|
83
|
+
transmission_mode: nil,
|
84
|
+
start_of_scope: 0,
|
85
|
+
end_of_scope: 0x1F,
|
86
|
+
segment_requests: [
|
87
|
+
[0, 0xF],
|
88
|
+
[0x10, 0x1F],
|
89
|
+
])
|
90
|
+
# puts buffer.formatted
|
91
|
+
expect(buffer.length).to eql 34
|
92
|
+
|
93
|
+
# By default the first 7 bytes are the header
|
94
|
+
# This assumes 1 byte per entity ID and sequence number
|
95
|
+
|
96
|
+
# Directive Code
|
97
|
+
expect(buffer[7].unpack('C')[0]).to eql 8 # NAK per Table 5-4
|
98
|
+
# Start of scope
|
99
|
+
expect(buffer[8..11].unpack('N')[0]).to eql 0
|
100
|
+
# End of scope
|
101
|
+
expect(buffer[12..15].unpack('N')[0]).to eql 0x1F
|
102
|
+
# Segment requests
|
103
|
+
expect(buffer[16..19].unpack('N')[0]).to eql 0
|
104
|
+
expect(buffer[20..23].unpack('N')[0]).to eql 0xF
|
105
|
+
expect(buffer[24..27].unpack('N')[0]).to eql 0x10
|
106
|
+
expect(buffer[28..31].unpack('N')[0]).to eql 0x1F
|
107
|
+
|
108
|
+
hash = {}
|
109
|
+
# decom takes just the NAK specific part of the buffer
|
110
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
111
|
+
CfdpPdu.decom_nak_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
112
|
+
expect(hash['START_OF_SCOPE']).to eql 0
|
113
|
+
expect(hash['END_OF_SCOPE']).to eql 0x1F
|
114
|
+
expect(hash['SEGMENT_REQUESTS']).to eql [
|
115
|
+
{
|
116
|
+
"START_OFFSET" => 0,
|
117
|
+
"END_OFFSET" => 0xF,
|
118
|
+
},
|
119
|
+
{
|
120
|
+
"START_OFFSET" => 0x10,
|
121
|
+
"END_OFFSET" => 0x1F,
|
122
|
+
}
|
123
|
+
]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,94 @@
|
|
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 'cfdp_pdu'
|
19
|
+
require 'openc3/models/microservice_model'
|
20
|
+
require 'openc3/utilities/store_autoload'
|
21
|
+
|
22
|
+
RSpec.describe CfdpPdu, type: :model do
|
23
|
+
before(:each) do
|
24
|
+
mock_redis()
|
25
|
+
@source_entity_id = 1
|
26
|
+
@destination_entity_id = 2
|
27
|
+
ENV['OPENC3_MICROSERVICE_NAME'] = 'DEFAULT__API__CFDP'
|
28
|
+
# Create the model that is consumed by CfdpMib.setup
|
29
|
+
model = OpenC3::MicroserviceModel.new(name: ENV['OPENC3_MICROSERVICE_NAME'], scope: "DEFAULT",
|
30
|
+
options: [
|
31
|
+
["source_entity_id", @source_entity_id],
|
32
|
+
["destination_entity_id", @destination_entity_id],
|
33
|
+
["root_path", SPEC_DIR],
|
34
|
+
],
|
35
|
+
)
|
36
|
+
model.create
|
37
|
+
CfdpMib.setup
|
38
|
+
end
|
39
|
+
|
40
|
+
# Validate Table 5-12: Prompt PDU Contents
|
41
|
+
describe "build_prompt_pdu" do
|
42
|
+
it "builds a Prompt PDU with nak response" do
|
43
|
+
buffer = CfdpPdu.build_prompt_pdu(
|
44
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
45
|
+
transaction_seq_num: 1,
|
46
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
47
|
+
segmentation_control: "NOT_PRESERVED",
|
48
|
+
transmission_mode: nil,
|
49
|
+
response_required: 'NAK')
|
50
|
+
# puts buffer.formatted
|
51
|
+
expect(buffer.length).to eql 11
|
52
|
+
|
53
|
+
# By default the first 7 bytes are the header
|
54
|
+
# This assumes 1 byte per entity ID and sequence number
|
55
|
+
|
56
|
+
# Directive Code
|
57
|
+
expect(buffer[7].unpack('C')[0]).to eql 9 # Prompt per Table 5-4
|
58
|
+
# Response required
|
59
|
+
expect(buffer[8].unpack('C')[0] >> 7).to eql 0
|
60
|
+
|
61
|
+
hash = {}
|
62
|
+
# decom takes just the Prompt specific part of the buffer
|
63
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
64
|
+
CfdpPdu.decom_prompt_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
65
|
+
expect(hash['RESPONSE_REQUIRED']).to eql 'NAK'
|
66
|
+
end
|
67
|
+
|
68
|
+
it "builds a Prompt PDU with keep alive response" do
|
69
|
+
buffer = CfdpPdu.build_prompt_pdu(
|
70
|
+
source_entity: CfdpMib.entity(@source_entity_id),
|
71
|
+
transaction_seq_num: 1,
|
72
|
+
destination_entity: CfdpMib.entity(@destination_entity_id),
|
73
|
+
segmentation_control: "NOT_PRESERVED",
|
74
|
+
transmission_mode: nil,
|
75
|
+
response_required: 'KEEP_ALIVE')
|
76
|
+
# puts buffer.formatted
|
77
|
+
expect(buffer.length).to eql 11
|
78
|
+
|
79
|
+
# By default the first 7 bytes are the header
|
80
|
+
# This assumes 1 byte per entity ID and sequence number
|
81
|
+
|
82
|
+
# Directive Code
|
83
|
+
expect(buffer[7].unpack('C')[0]).to eql 9 # Prompt per Table 5-4
|
84
|
+
# Response required
|
85
|
+
expect(buffer[8].unpack('C')[0] >> 7).to eql 1
|
86
|
+
|
87
|
+
hash = {}
|
88
|
+
# decom takes just the Prompt specific part of the buffer
|
89
|
+
# so start at offset 8 and ignore the 2 checksum bytes
|
90
|
+
CfdpPdu.decom_prompt_pdu_contents(CfdpPdu.new(crcs_required: false), hash, buffer[8..-3])
|
91
|
+
expect(hash['RESPONSE_REQUIRED']).to eql 'KEEP_ALIVE'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,111 @@
|
|
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
|
+
|
19
|
+
RSpec.describe CfdpPdu, type: :model do
|
20
|
+
# Validate Table 5-1: Fixed PDU Header Fields
|
21
|
+
describe "initialize" do
|
22
|
+
it "builds a PDU with crcs" do
|
23
|
+
pdu = CfdpPdu.new(crcs_required: true)
|
24
|
+
expect(pdu.items.keys).to include("CRC")
|
25
|
+
expect(pdu.buffer.length).to eql 6
|
26
|
+
end
|
27
|
+
|
28
|
+
it "builds a PDU without crcs" do
|
29
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
30
|
+
expect(pdu.items.keys).to_not include("CRC")
|
31
|
+
expect(pdu.buffer.length).to eql 4
|
32
|
+
end
|
33
|
+
|
34
|
+
it "sets the version field" do
|
35
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
36
|
+
pdu.enable_method_missing
|
37
|
+
pdu.version = 1
|
38
|
+
expect(pdu.buffer[0].unpack('C')[0] >> 5).to eql 1
|
39
|
+
end
|
40
|
+
|
41
|
+
it "sets the PDU type" do
|
42
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
43
|
+
pdu.enable_method_missing
|
44
|
+
pdu.type = 1
|
45
|
+
expect(pdu.buffer[0].unpack('C')[0] >> 4).to eql 1
|
46
|
+
end
|
47
|
+
|
48
|
+
it "sets the direction" do
|
49
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
50
|
+
pdu.enable_method_missing
|
51
|
+
pdu.direction = 1
|
52
|
+
expect(pdu.buffer[0].unpack('C')[0] >> 3).to eql 1
|
53
|
+
end
|
54
|
+
|
55
|
+
it "sets the transmission mode" do
|
56
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
57
|
+
pdu.enable_method_missing
|
58
|
+
pdu.transmission_mode = 1
|
59
|
+
expect(pdu.buffer[0].unpack('C')[0] >> 2).to eql 1
|
60
|
+
end
|
61
|
+
|
62
|
+
it "sets the crc flag" do
|
63
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
64
|
+
pdu.enable_method_missing
|
65
|
+
pdu.crc_flag = 1
|
66
|
+
expect(pdu.buffer[0].unpack('C')[0] >> 1).to eql 1
|
67
|
+
end
|
68
|
+
|
69
|
+
it "sets the large file flag" do
|
70
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
71
|
+
pdu.enable_method_missing
|
72
|
+
pdu.large_file_flag = 1
|
73
|
+
expect(pdu.buffer[0].unpack('C')[0]).to eql 1
|
74
|
+
end
|
75
|
+
|
76
|
+
it "sets the length" do
|
77
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
78
|
+
pdu.enable_method_missing
|
79
|
+
pdu.pdu_data_length = 0x1234
|
80
|
+
expect(pdu.buffer[1..2].unpack('n')[0]).to eql 0x1234
|
81
|
+
end
|
82
|
+
|
83
|
+
it "sets the segmentation control" do
|
84
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
85
|
+
pdu.enable_method_missing
|
86
|
+
pdu.segmentation_control = 1
|
87
|
+
expect(pdu.buffer[3].unpack('C')[0] >> 7).to eql 1
|
88
|
+
end
|
89
|
+
|
90
|
+
it "sets the entity id length" do
|
91
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
92
|
+
pdu.enable_method_missing
|
93
|
+
pdu.entity_id_length = 7
|
94
|
+
expect(pdu.buffer[3].unpack('C')[0] >> 4).to eql 7
|
95
|
+
end
|
96
|
+
|
97
|
+
it "sets the segment metadata flag" do
|
98
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
99
|
+
pdu.enable_method_missing
|
100
|
+
pdu.segment_metadata_flag = 1
|
101
|
+
expect(pdu.buffer[3].unpack('C')[0] >> 3).to eql 1
|
102
|
+
end
|
103
|
+
|
104
|
+
it "sets the sequence number length" do
|
105
|
+
pdu = CfdpPdu.new(crcs_required: false)
|
106
|
+
pdu.enable_method_missing
|
107
|
+
pdu.sequence_number_length = 7
|
108
|
+
expect(pdu.buffer[3].unpack('C')[0]).to eql 7
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|