pycfdp 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cfdp/__init__.py +106 -0
- cfdp/checksum.py +94 -0
- cfdp/constants.py +153 -0
- cfdp/core.py +895 -0
- cfdp/event.py +55 -0
- cfdp/filestore/__init__.py +2 -0
- cfdp/filestore/base.py +99 -0
- cfdp/filestore/native.py +162 -0
- cfdp/meta/__init__.py +3 -0
- cfdp/meta/datafield.py +11 -0
- cfdp/meta/filestore.py +127 -0
- cfdp/meta/message.py +659 -0
- cfdp/pdu/__init__.py +8 -0
- cfdp/pdu/ack.py +90 -0
- cfdp/pdu/eof.py +123 -0
- cfdp/pdu/filedata.py +90 -0
- cfdp/pdu/finished.py +99 -0
- cfdp/pdu/header.py +159 -0
- cfdp/pdu/keep_alive.py +108 -0
- cfdp/pdu/metadata.py +211 -0
- cfdp/pdu/nak.py +220 -0
- cfdp/pdu/prompt.py +75 -0
- cfdp/pure/__init__.py +68 -0
- cfdp/pure/config.py +141 -0
- cfdp/pure/indications.py +130 -0
- cfdp/pure/machines/__init__.py +25 -0
- cfdp/pure/machines/receiver1.py +433 -0
- cfdp/pure/machines/receiver2.py +893 -0
- cfdp/pure/machines/sender1.py +400 -0
- cfdp/pure/machines/sender2.py +774 -0
- cfdp/pure/outputs.py +203 -0
- cfdp/pure/query_port.py +48 -0
- cfdp/pure/transaction.py +43 -0
- cfdp/py.typed +0 -0
- cfdp/remote_entity_config.py +12 -0
- cfdp/shell/__init__.py +44 -0
- cfdp/shell/checksum_service.py +136 -0
- cfdp/shell/dispatcher.py +185 -0
- cfdp/shell/effect_executor.py +285 -0
- cfdp/shell/machine_registry.py +165 -0
- cfdp/shell/query_port.py +70 -0
- cfdp/shell/timer_service.py +252 -0
- cfdp/transaction_handle.py +50 -0
- cfdp/transport/__init__.py +0 -0
- cfdp/transport/base.py +12 -0
- cfdp/transport/spacepacket.py +47 -0
- cfdp/transport/udp.py +90 -0
- cfdp/transport/zmq.py +54 -0
- pycfdp-0.2.2.dist-info/METADATA +76 -0
- pycfdp-0.2.2.dist-info/RECORD +53 -0
- pycfdp-0.2.2.dist-info/WHEEL +5 -0
- pycfdp-0.2.2.dist-info/licenses/LICENSE.txt +19 -0
- pycfdp-0.2.2.dist-info/top_level.txt +1 -0
cfdp/__init__.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
5
|
+
from .constants import (
|
|
6
|
+
DEFAULT_ACK_TIMER_EXPIRATION_LIMIT,
|
|
7
|
+
DEFAULT_ACK_TIMER_LIMIT,
|
|
8
|
+
DEFAULT_FAULT_HANDLERS,
|
|
9
|
+
DEFAULT_INACTIVITY_TIMEOUT,
|
|
10
|
+
DEFAULT_MAX_FILE_SEGMENT_LEN,
|
|
11
|
+
DEFAULT_NAK_TIMER_EXPIRATION_LIMIT,
|
|
12
|
+
DEFAULT_NAK_TIMER_INTERVAL,
|
|
13
|
+
ActionCode,
|
|
14
|
+
ChecksumType,
|
|
15
|
+
ConditionCode,
|
|
16
|
+
DeliveryCode,
|
|
17
|
+
Direction,
|
|
18
|
+
FaultHandlerAction,
|
|
19
|
+
FileStatus,
|
|
20
|
+
MachineState,
|
|
21
|
+
TransactionStatus,
|
|
22
|
+
TransmissionMode,
|
|
23
|
+
)
|
|
24
|
+
from .core import CfdpEntity
|
|
25
|
+
from .event import Event, EventType
|
|
26
|
+
from .meta import process_user_message
|
|
27
|
+
from .meta.filestore import FilestoreRequest
|
|
28
|
+
from .meta.message import (
|
|
29
|
+
DirectoryListingRequest,
|
|
30
|
+
DirectoryListingResponse,
|
|
31
|
+
MessageToUser,
|
|
32
|
+
OriginatingTransactionId,
|
|
33
|
+
ProxyPutCancel,
|
|
34
|
+
ProxyPutRequest,
|
|
35
|
+
RemoteResumeRequest,
|
|
36
|
+
RemoteResumeResponse,
|
|
37
|
+
RemoteSuspendRequest,
|
|
38
|
+
RemoteSuspendResponse,
|
|
39
|
+
UnrecognizedMessage,
|
|
40
|
+
)
|
|
41
|
+
from .pdu import (
|
|
42
|
+
AckPdu,
|
|
43
|
+
EofPdu,
|
|
44
|
+
FiledataPdu,
|
|
45
|
+
FinishedPdu,
|
|
46
|
+
KeepAlivePdu,
|
|
47
|
+
MetadataPdu,
|
|
48
|
+
NakPdu,
|
|
49
|
+
PduHeader,
|
|
50
|
+
PromptPdu,
|
|
51
|
+
)
|
|
52
|
+
from .pure.transaction import Transaction
|
|
53
|
+
from .remote_entity_config import RemoteEntityConfig
|
|
54
|
+
from .transaction_handle import TransactionHandle
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
# Core public API
|
|
58
|
+
"CfdpEntity",
|
|
59
|
+
"RemoteEntityConfig",
|
|
60
|
+
"TransactionHandle",
|
|
61
|
+
# Constants / enums used in the public interface
|
|
62
|
+
"TransmissionMode",
|
|
63
|
+
"Direction",
|
|
64
|
+
"ConditionCode",
|
|
65
|
+
"MachineState",
|
|
66
|
+
"DeliveryCode",
|
|
67
|
+
"FileStatus",
|
|
68
|
+
"TransactionStatus",
|
|
69
|
+
"FaultHandlerAction",
|
|
70
|
+
"ChecksumType",
|
|
71
|
+
"ActionCode",
|
|
72
|
+
"FilestoreRequest",
|
|
73
|
+
"DEFAULT_FAULT_HANDLERS",
|
|
74
|
+
"DEFAULT_INACTIVITY_TIMEOUT",
|
|
75
|
+
"DEFAULT_ACK_TIMER_LIMIT",
|
|
76
|
+
"DEFAULT_ACK_TIMER_EXPIRATION_LIMIT",
|
|
77
|
+
"DEFAULT_NAK_TIMER_INTERVAL",
|
|
78
|
+
"DEFAULT_NAK_TIMER_EXPIRATION_LIMIT",
|
|
79
|
+
"DEFAULT_MAX_FILE_SEGMENT_LEN",
|
|
80
|
+
# PDU types (needed for advanced use)
|
|
81
|
+
"PduHeader",
|
|
82
|
+
"MetadataPdu",
|
|
83
|
+
"FiledataPdu",
|
|
84
|
+
"EofPdu",
|
|
85
|
+
"FinishedPdu",
|
|
86
|
+
"NakPdu",
|
|
87
|
+
"AckPdu",
|
|
88
|
+
"KeepAlivePdu",
|
|
89
|
+
"PromptPdu",
|
|
90
|
+
# Meta / message types
|
|
91
|
+
"MessageToUser",
|
|
92
|
+
"ProxyPutRequest",
|
|
93
|
+
"ProxyPutCancel",
|
|
94
|
+
"DirectoryListingRequest",
|
|
95
|
+
"DirectoryListingResponse",
|
|
96
|
+
"OriginatingTransactionId",
|
|
97
|
+
"RemoteSuspendRequest",
|
|
98
|
+
"RemoteSuspendResponse",
|
|
99
|
+
"RemoteResumeRequest",
|
|
100
|
+
"RemoteResumeResponse",
|
|
101
|
+
"UnrecognizedMessage",
|
|
102
|
+
# Internal types occasionally accessed in tests / advanced usage
|
|
103
|
+
"Event",
|
|
104
|
+
"EventType",
|
|
105
|
+
"Transaction",
|
|
106
|
+
]
|
cfdp/checksum.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import binascii
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UnsupportedChecksumType(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PduCrcError(Exception):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _build_crc32c_table():
|
|
14
|
+
poly = 0x82F63B78 # reversed Castagnoli polynomial
|
|
15
|
+
table = []
|
|
16
|
+
for i in range(256):
|
|
17
|
+
crc = i
|
|
18
|
+
for _ in range(8):
|
|
19
|
+
crc = (crc >> 1) ^ poly if crc & 1 else crc >> 1
|
|
20
|
+
table.append(crc)
|
|
21
|
+
return table
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_CRC32C_TABLE = _build_crc32c_table()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def calculate_checksum(data_iter: Iterable[bytes], checksum_type: int) -> int:
|
|
28
|
+
"""Compute a CFDP file checksum over an iterable of byte chunks."""
|
|
29
|
+
if checksum_type == 0: # Modular (CCSDS 727.0-B-5 Annex F)
|
|
30
|
+
checksum = 0
|
|
31
|
+
buf = b""
|
|
32
|
+
for chunk in data_iter:
|
|
33
|
+
buf += chunk
|
|
34
|
+
n = (len(buf) // 4) * 4
|
|
35
|
+
for i in range(0, n, 4):
|
|
36
|
+
checksum += int.from_bytes(buf[i : i + 4], "big")
|
|
37
|
+
buf = buf[n:]
|
|
38
|
+
if buf:
|
|
39
|
+
padded = buf + b"\x00" * (4 - len(buf))
|
|
40
|
+
checksum += int.from_bytes(padded, "big")
|
|
41
|
+
return checksum % (2**32)
|
|
42
|
+
|
|
43
|
+
elif checksum_type == 2: # CRC-32C (Castagnoli)
|
|
44
|
+
crc = 0xFFFFFFFF
|
|
45
|
+
for chunk in data_iter:
|
|
46
|
+
for byte in chunk:
|
|
47
|
+
crc = (crc >> 8) ^ _CRC32C_TABLE[(crc ^ byte) & 0xFF]
|
|
48
|
+
return crc ^ 0xFFFFFFFF
|
|
49
|
+
|
|
50
|
+
elif checksum_type == 3: # IEEE CRC-32
|
|
51
|
+
crc = 0
|
|
52
|
+
for chunk in data_iter:
|
|
53
|
+
crc = binascii.crc32(chunk, crc)
|
|
54
|
+
return crc & 0xFFFFFFFF
|
|
55
|
+
|
|
56
|
+
elif checksum_type == 15: # Null
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
raise UnsupportedChecksumType(f"Unsupported checksum type: {checksum_type}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def crc16_ccitt(data: bytes) -> int:
|
|
64
|
+
"""CRC-16/CCITT-FALSE (poly=0x1021, init=0xFFFF) used for PDU integrity."""
|
|
65
|
+
crc = 0xFFFF
|
|
66
|
+
for byte in data:
|
|
67
|
+
crc ^= byte << 8
|
|
68
|
+
for _ in range(8):
|
|
69
|
+
crc = (crc << 1) ^ 0x1021 if crc & 0x8000 else crc << 1
|
|
70
|
+
crc &= 0xFFFF
|
|
71
|
+
return crc
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def strip_pdu_crc(pdu: bytes) -> bytes:
|
|
75
|
+
"""If crc_flag is set, verify the trailing 2-byte CRC and return the PDU without it."""
|
|
76
|
+
if not (pdu[0] >> 1) & 0x01:
|
|
77
|
+
return pdu
|
|
78
|
+
received = int.from_bytes(pdu[-2:], "big")
|
|
79
|
+
computed = crc16_ccitt(pdu[:-2])
|
|
80
|
+
if received != computed:
|
|
81
|
+
raise PduCrcError(
|
|
82
|
+
f"PDU CRC mismatch: computed {computed:#06x}, received {received:#06x}"
|
|
83
|
+
)
|
|
84
|
+
return pdu[:-2]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def finalize_pdu(pdu_header, databytes: bytes) -> bytes:
|
|
88
|
+
"""Set pdu_data_field_length, encode header+data, append CRC-16 if crc_flag=1."""
|
|
89
|
+
crc_flag = pdu_header.crc_flag
|
|
90
|
+
pdu_header.pdu_data_field_length = len(databytes) + (2 if crc_flag else 0)
|
|
91
|
+
pdu_bytes = pdu_header.encode() + databytes
|
|
92
|
+
if crc_flag:
|
|
93
|
+
pdu_bytes += crc16_ccitt(pdu_bytes).to_bytes(2, "big")
|
|
94
|
+
return pdu_bytes
|
cfdp/constants.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
class ProtocolVersion:
|
|
2
|
+
VERSION_1 = 0
|
|
3
|
+
VERSION_2 = 1
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TransmissionMode:
|
|
7
|
+
ACKNOWLEDGED = 0
|
|
8
|
+
UNACKNOWLEDGED = 1
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PduTypeCode:
|
|
12
|
+
FILE_DIRECTIVE = 0
|
|
13
|
+
FILE_DATA = 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Direction:
|
|
17
|
+
TOWARD_RECEIVER = 0
|
|
18
|
+
TOWARD_SENDER = 1
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DirectiveCode:
|
|
22
|
+
EOF = 0x04
|
|
23
|
+
FINISHED = 0x05
|
|
24
|
+
ACK = 0x06
|
|
25
|
+
METADATA = 0x07
|
|
26
|
+
NAK = 0x08
|
|
27
|
+
PROMPT = 0x09
|
|
28
|
+
KEEP_ALIVE = 0x0C
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DirectiveSubTypeCode:
|
|
32
|
+
ACK_OTHERS = 0x00
|
|
33
|
+
ACK_FINISHED = 0x01
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConditionCode:
|
|
37
|
+
NO_ERROR = 0
|
|
38
|
+
POSITIVE_ACK_LIMIT_REACHED = 1
|
|
39
|
+
KEEP_ALIVE_LIMIT_REACHED = 2
|
|
40
|
+
INVALID_TRANSMISSION_MODE = 3
|
|
41
|
+
FILESTORE_REJECTION = 4
|
|
42
|
+
FILE_CHECKSUM_FAILURE = 5
|
|
43
|
+
FILE_SIZE_ERROR = 6
|
|
44
|
+
NAK_LIMIT_REACHED = 7
|
|
45
|
+
INACTIVITY_DETECTED = 8
|
|
46
|
+
INVALID_FILE_STRUCTURE = 9
|
|
47
|
+
CHECK_LIMIT_REACHED = 10
|
|
48
|
+
UNSUPPORTED_CHECKSUM_TYPE = 11
|
|
49
|
+
SUSPEND_REQUEST_RECEIVED = 14
|
|
50
|
+
CANCEL_REQUEST_RECEIVED = 15
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MachineState:
|
|
54
|
+
SEND_METADATA = "SEND_METADATA"
|
|
55
|
+
SEND_FILE = "SEND_FILE"
|
|
56
|
+
SEND_EOF = "SEND_EOF"
|
|
57
|
+
TRANSACTION_CANCELLED = "TRANSACTION_CANCELLED"
|
|
58
|
+
WAIT_FOR_MD = "WAIT_FOR_MD"
|
|
59
|
+
WAIT_FOR_EOF = "WAIT_FOR_EOF"
|
|
60
|
+
GET_MISSING_DATA = "GET_MISSING_DATA"
|
|
61
|
+
SEND_FINISHED = "SEND_FINISHED"
|
|
62
|
+
COMPLETED = "COMPLETED"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DeliveryCode:
|
|
66
|
+
DATA_COMPLETE = 0
|
|
67
|
+
DATA_INCOMPLETE = 1
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class FileStatus:
|
|
71
|
+
DISCARDED_DELIBERATELY = 0
|
|
72
|
+
DISCARDED_FILESTOR_REJECTION = 1
|
|
73
|
+
RETAINED_SUCCESSFULLY = 2
|
|
74
|
+
UNREPORTED = 3
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TransactionStatus:
|
|
78
|
+
UNDEFINED = 0
|
|
79
|
+
ACTIVE = 1
|
|
80
|
+
TERMINATED = 2
|
|
81
|
+
UNRECOGNIZED = 3
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ActionCode:
|
|
85
|
+
CREATE_FILE = 0
|
|
86
|
+
DELETE_FILE = 1
|
|
87
|
+
RENAME_FILE = 2
|
|
88
|
+
APPEND_FILE = 3
|
|
89
|
+
REPLACE_FILE = 4
|
|
90
|
+
CREATE_DIRECTORY = 5
|
|
91
|
+
REMOVE_DIRECTORY = 6
|
|
92
|
+
DENY_FILE = 7 # delete if present
|
|
93
|
+
DENY_DIRECTORY = 8 # remove if present
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TypeFieldCode:
|
|
97
|
+
FILESTORE_REQUEST = 0x00
|
|
98
|
+
FILESTORE_RESPONSE = 0x01
|
|
99
|
+
MESSAGE_TO_USER = 0x02
|
|
100
|
+
FAULT_HANDLER_OVERRIDES = 0x04
|
|
101
|
+
FLOW_LABEL = 0x05
|
|
102
|
+
ENTITY_ID = 0x06
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class MessageType:
|
|
106
|
+
PROXY_PUT_REQUEST = 0x00
|
|
107
|
+
PROXY_PUT_CANCEL = 0x09
|
|
108
|
+
ORIGINATING_TRANSACTION_ID = 0x0A
|
|
109
|
+
DIRECTORY_LISTING_REQUEST = 0x10
|
|
110
|
+
DIRECTORY_LISTING_RESPONSE = 0x11
|
|
111
|
+
REMOTE_SUSPEND_REQUEST = 0x30
|
|
112
|
+
REMOTE_SUSPEND_RESPONSE = 0x31
|
|
113
|
+
REMOTE_RESUME_REQUEST = 0x38
|
|
114
|
+
REMOTE_RESUME_RESPONSE = 0x39
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class FaultHandlerAction:
|
|
118
|
+
CANCEL = "CANCEL"
|
|
119
|
+
SUSPEND = "SUSPEND"
|
|
120
|
+
IGNORE = "IGNORE"
|
|
121
|
+
ABANDON = "ABANDON"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ChecksumType:
|
|
125
|
+
MODULAR = 0
|
|
126
|
+
PROXIMITY1 = 1
|
|
127
|
+
CRC32C = 2
|
|
128
|
+
IEEE = 3
|
|
129
|
+
NULL = 15
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
DEFAULT_MAX_FILE_SEGMENT_LEN = 800
|
|
133
|
+
DEFAULT_INACTIVITY_TIMEOUT = 30
|
|
134
|
+
DEFAULT_ACK_TIMER_LIMIT = 5
|
|
135
|
+
DEFAULT_ACK_TIMER_EXPIRATION_LIMIT = 3
|
|
136
|
+
DEFAULT_NAK_TIMER_INTERVAL = 5
|
|
137
|
+
DEFAULT_NAK_TIMER_EXPIRATION_LIMIT = 5
|
|
138
|
+
DEFAULT_FAULT_HANDLERS = {
|
|
139
|
+
ConditionCode.NO_ERROR: FaultHandlerAction.IGNORE,
|
|
140
|
+
ConditionCode.POSITIVE_ACK_LIMIT_REACHED: FaultHandlerAction.CANCEL,
|
|
141
|
+
ConditionCode.KEEP_ALIVE_LIMIT_REACHED: FaultHandlerAction.CANCEL,
|
|
142
|
+
ConditionCode.INVALID_TRANSMISSION_MODE: FaultHandlerAction.CANCEL,
|
|
143
|
+
ConditionCode.FILESTORE_REJECTION: FaultHandlerAction.CANCEL,
|
|
144
|
+
ConditionCode.FILE_CHECKSUM_FAILURE: FaultHandlerAction.CANCEL,
|
|
145
|
+
ConditionCode.FILE_SIZE_ERROR: FaultHandlerAction.CANCEL,
|
|
146
|
+
ConditionCode.NAK_LIMIT_REACHED: FaultHandlerAction.CANCEL,
|
|
147
|
+
ConditionCode.INACTIVITY_DETECTED: FaultHandlerAction.CANCEL,
|
|
148
|
+
ConditionCode.INVALID_FILE_STRUCTURE: FaultHandlerAction.CANCEL,
|
|
149
|
+
ConditionCode.CHECK_LIMIT_REACHED: FaultHandlerAction.CANCEL,
|
|
150
|
+
ConditionCode.UNSUPPORTED_CHECKSUM_TYPE: FaultHandlerAction.CANCEL,
|
|
151
|
+
ConditionCode.SUSPEND_REQUEST_RECEIVED: FaultHandlerAction.IGNORE,
|
|
152
|
+
ConditionCode.CANCEL_REQUEST_RECEIVED: FaultHandlerAction.IGNORE,
|
|
153
|
+
}
|