pycfdp 0.2.2__tar.gz

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.
Files changed (98) hide show
  1. pycfdp-0.2.2/LICENSE.txt +19 -0
  2. pycfdp-0.2.2/PKG-INFO +76 -0
  3. pycfdp-0.2.2/README.md +58 -0
  4. pycfdp-0.2.2/pyproject.toml +68 -0
  5. pycfdp-0.2.2/setup.cfg +4 -0
  6. pycfdp-0.2.2/src/cfdp/__init__.py +106 -0
  7. pycfdp-0.2.2/src/cfdp/checksum.py +94 -0
  8. pycfdp-0.2.2/src/cfdp/constants.py +153 -0
  9. pycfdp-0.2.2/src/cfdp/core.py +895 -0
  10. pycfdp-0.2.2/src/cfdp/event.py +55 -0
  11. pycfdp-0.2.2/src/cfdp/filestore/__init__.py +2 -0
  12. pycfdp-0.2.2/src/cfdp/filestore/base.py +99 -0
  13. pycfdp-0.2.2/src/cfdp/filestore/native.py +162 -0
  14. pycfdp-0.2.2/src/cfdp/meta/__init__.py +3 -0
  15. pycfdp-0.2.2/src/cfdp/meta/datafield.py +11 -0
  16. pycfdp-0.2.2/src/cfdp/meta/filestore.py +127 -0
  17. pycfdp-0.2.2/src/cfdp/meta/message.py +659 -0
  18. pycfdp-0.2.2/src/cfdp/pdu/__init__.py +8 -0
  19. pycfdp-0.2.2/src/cfdp/pdu/ack.py +90 -0
  20. pycfdp-0.2.2/src/cfdp/pdu/eof.py +123 -0
  21. pycfdp-0.2.2/src/cfdp/pdu/filedata.py +90 -0
  22. pycfdp-0.2.2/src/cfdp/pdu/finished.py +99 -0
  23. pycfdp-0.2.2/src/cfdp/pdu/header.py +159 -0
  24. pycfdp-0.2.2/src/cfdp/pdu/keep_alive.py +108 -0
  25. pycfdp-0.2.2/src/cfdp/pdu/metadata.py +211 -0
  26. pycfdp-0.2.2/src/cfdp/pdu/nak.py +220 -0
  27. pycfdp-0.2.2/src/cfdp/pdu/prompt.py +75 -0
  28. pycfdp-0.2.2/src/cfdp/pure/__init__.py +68 -0
  29. pycfdp-0.2.2/src/cfdp/pure/config.py +141 -0
  30. pycfdp-0.2.2/src/cfdp/pure/indications.py +130 -0
  31. pycfdp-0.2.2/src/cfdp/pure/machines/__init__.py +25 -0
  32. pycfdp-0.2.2/src/cfdp/pure/machines/receiver1.py +433 -0
  33. pycfdp-0.2.2/src/cfdp/pure/machines/receiver2.py +893 -0
  34. pycfdp-0.2.2/src/cfdp/pure/machines/sender1.py +400 -0
  35. pycfdp-0.2.2/src/cfdp/pure/machines/sender2.py +774 -0
  36. pycfdp-0.2.2/src/cfdp/pure/outputs.py +203 -0
  37. pycfdp-0.2.2/src/cfdp/pure/query_port.py +48 -0
  38. pycfdp-0.2.2/src/cfdp/pure/transaction.py +43 -0
  39. pycfdp-0.2.2/src/cfdp/py.typed +0 -0
  40. pycfdp-0.2.2/src/cfdp/remote_entity_config.py +12 -0
  41. pycfdp-0.2.2/src/cfdp/shell/__init__.py +44 -0
  42. pycfdp-0.2.2/src/cfdp/shell/checksum_service.py +136 -0
  43. pycfdp-0.2.2/src/cfdp/shell/dispatcher.py +185 -0
  44. pycfdp-0.2.2/src/cfdp/shell/effect_executor.py +285 -0
  45. pycfdp-0.2.2/src/cfdp/shell/machine_registry.py +165 -0
  46. pycfdp-0.2.2/src/cfdp/shell/query_port.py +70 -0
  47. pycfdp-0.2.2/src/cfdp/shell/timer_service.py +252 -0
  48. pycfdp-0.2.2/src/cfdp/transaction_handle.py +50 -0
  49. pycfdp-0.2.2/src/cfdp/transport/__init__.py +0 -0
  50. pycfdp-0.2.2/src/cfdp/transport/base.py +12 -0
  51. pycfdp-0.2.2/src/cfdp/transport/spacepacket.py +47 -0
  52. pycfdp-0.2.2/src/cfdp/transport/udp.py +90 -0
  53. pycfdp-0.2.2/src/cfdp/transport/zmq.py +54 -0
  54. pycfdp-0.2.2/src/pycfdp.egg-info/PKG-INFO +76 -0
  55. pycfdp-0.2.2/src/pycfdp.egg-info/SOURCES.txt +96 -0
  56. pycfdp-0.2.2/src/pycfdp.egg-info/dependency_links.txt +1 -0
  57. pycfdp-0.2.2/src/pycfdp.egg-info/requires.txt +10 -0
  58. pycfdp-0.2.2/src/pycfdp.egg-info/top_level.txt +1 -0
  59. pycfdp-0.2.2/tests/test_async_checksum.py +554 -0
  60. pycfdp-0.2.2/tests/test_callback_api.py +241 -0
  61. pycfdp-0.2.2/tests/test_checksum.py +206 -0
  62. pycfdp-0.2.2/tests/test_chunked_copy.py +120 -0
  63. pycfdp-0.2.2/tests/test_class1_shell_seam.py +331 -0
  64. pycfdp-0.2.2/tests/test_class2_shell_seam.py +784 -0
  65. pycfdp-0.2.2/tests/test_cpu_yield.py +104 -0
  66. pycfdp-0.2.2/tests/test_direct_write_shell_seam.py +189 -0
  67. pycfdp-0.2.2/tests/test_e2e_suite.py +410 -0
  68. pycfdp-0.2.2/tests/test_entity_lifecycle.py +188 -0
  69. pycfdp-0.2.2/tests/test_fault_handlers.py +27 -0
  70. pycfdp-0.2.2/tests/test_fault_location_tlv.py +119 -0
  71. pycfdp-0.2.2/tests/test_file_status.py +144 -0
  72. pycfdp-0.2.2/tests/test_filestore_abc.py +217 -0
  73. pycfdp-0.2.2/tests/test_large_file_pdu.py +120 -0
  74. pycfdp-0.2.2/tests/test_native_filehandle.py +21 -0
  75. pycfdp-0.2.2/tests/test_pacing_timer.py +99 -0
  76. pycfdp-0.2.2/tests/test_progress_tracking.py +147 -0
  77. pycfdp-0.2.2/tests/test_public_api.py +70 -0
  78. pycfdp-0.2.2/tests/test_pure_receiver1.py +644 -0
  79. pycfdp-0.2.2/tests/test_pure_receiver2.py +1062 -0
  80. pycfdp-0.2.2/tests/test_pure_sender1.py +395 -0
  81. pycfdp-0.2.2/tests/test_pure_sender2.py +682 -0
  82. pycfdp-0.2.2/tests/test_pure_taxonomy.py +344 -0
  83. pycfdp-0.2.2/tests/test_put_unimplemented.py +75 -0
  84. pycfdp-0.2.2/tests/test_remote_entity_config.py +136 -0
  85. pycfdp-0.2.2/tests/test_remote_entity_timers.py +273 -0
  86. pycfdp-0.2.2/tests/test_series_F1.py +775 -0
  87. pycfdp-0.2.2/tests/test_series_F2.py +850 -0
  88. pycfdp-0.2.2/tests/test_series_F3.py +720 -0
  89. pycfdp-0.2.2/tests/test_series_F4.py +457 -0
  90. pycfdp-0.2.2/tests/test_spacepacket.py +59 -0
  91. pycfdp-0.2.2/tests/test_thread_safe_routing.py +212 -0
  92. pycfdp-0.2.2/tests/test_thread_safety.py +243 -0
  93. pycfdp-0.2.2/tests/test_timer_service.py +335 -0
  94. pycfdp-0.2.2/tests/test_transaction_handle.py +229 -0
  95. pycfdp-0.2.2/tests/test_transmission_mode_default.py +48 -0
  96. pycfdp-0.2.2/tests/test_transport_abc.py +108 -0
  97. pycfdp-0.2.2/tests/test_udp_transport_docs.py +19 -0
  98. pycfdp-0.2.2/tests/test_zmq.py +80 -0
@@ -0,0 +1,19 @@
1
+ MIT License
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
pycfdp-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycfdp
3
+ Version: 0.2.2
4
+ Summary: Python implementation of CCSDS File Delivery Protocol (Modernized Fork)
5
+ Author-email: Alex Pope <alexpopester@gmail.com>, "LibreCube (Original Codebase)" <info@librecube.org>
6
+ License: MIT License
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE.txt
10
+ Provides-Extra: zmq
11
+ Requires-Dist: pyzmq; extra == "zmq"
12
+ Provides-Extra: spacepacket
13
+ Requires-Dist: spacepacket; extra == "spacepacket"
14
+ Provides-Extra: all
15
+ Requires-Dist: pyzmq; extra == "all"
16
+ Requires-Dist: spacepacket; extra == "all"
17
+ Dynamic: license-file
18
+
19
+ # Python CFDP, Version 0.2
20
+
21
+ The CCSDS File Delivery Protocol (CFDP) was developed by the
22
+ [Consultative Committee for Space Data Systems (CCSDS)](https://public.ccsds.org).
23
+ The CFDP protocol provides reliable transfer of files from one endpoint to
24
+ another and has been designed to work well over space links that suffer
25
+ from outtakes and long delays. The basic operation of CFDP is to transfer a
26
+ file from a sender to a receiver (both referred to as CFDP entities). The
27
+ sender and receiver must be configured and running at the same time to perform
28
+ a file transfer. It can be used to perform space to ground, ground to space,
29
+ space to space, and ground to ground file transfers. For example it can be used
30
+ to transfer science data from satellite over mission control to the science
31
+ center in an automated fashion with minimal to no human intervention.
32
+
33
+ This Python module is an implementation of the CCSDS File Delivery Protocol.
34
+ It supports all features as outlined in the latest version of the [CFDP Blue Book](docs/727x0b5.pdf). These are:
35
+
36
+ - Class 1 (unacknowledged) file transfer
37
+ - Class 2 (acknowledged) file transfer
38
+ - Filestore requests
39
+ - Proxy operations
40
+ - Directory listing request
41
+ - Native filestore implementation
42
+ - UDP as default transport layer (optional ZMQ)
43
+
44
+ ## Installation
45
+
46
+ Clone the repository and then install via pip:
47
+
48
+ ```
49
+ pip install cfdp
50
+ ```
51
+
52
+ > Depending on the transport layer to use with CFDP, one needs to install additional dependencies.
53
+
54
+ ## Documentation
55
+
56
+ Find the detailed documentation [here](docs/README.md).
57
+
58
+ ## Tests
59
+
60
+ The protocol is tested for cross-support as outlined in CCSDS CFDP Yellow Book.
61
+ See [tests/README.md](tests/README.md) for details.
62
+
63
+ ## Contribute
64
+
65
+ To learn more on how to successfully contribute please read the contributing
66
+ information in the [LibreCube documentation](https://librecube.gitlab.io/).
67
+
68
+ ## Support
69
+
70
+ If you are having issues, please let us know. Reach us at
71
+ [Matrix](https://app.element.io/#/room/#librecube.org:matrix.org)
72
+ or via [Email](mailto:info@librecube.org).
73
+
74
+ ## License
75
+
76
+ The project is licensed under the MIT license. See the [LICENSE](./LICENSE.txt) file for details.
pycfdp-0.2.2/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Python CFDP, Version 0.2
2
+
3
+ The CCSDS File Delivery Protocol (CFDP) was developed by the
4
+ [Consultative Committee for Space Data Systems (CCSDS)](https://public.ccsds.org).
5
+ The CFDP protocol provides reliable transfer of files from one endpoint to
6
+ another and has been designed to work well over space links that suffer
7
+ from outtakes and long delays. The basic operation of CFDP is to transfer a
8
+ file from a sender to a receiver (both referred to as CFDP entities). The
9
+ sender and receiver must be configured and running at the same time to perform
10
+ a file transfer. It can be used to perform space to ground, ground to space,
11
+ space to space, and ground to ground file transfers. For example it can be used
12
+ to transfer science data from satellite over mission control to the science
13
+ center in an automated fashion with minimal to no human intervention.
14
+
15
+ This Python module is an implementation of the CCSDS File Delivery Protocol.
16
+ It supports all features as outlined in the latest version of the [CFDP Blue Book](docs/727x0b5.pdf). These are:
17
+
18
+ - Class 1 (unacknowledged) file transfer
19
+ - Class 2 (acknowledged) file transfer
20
+ - Filestore requests
21
+ - Proxy operations
22
+ - Directory listing request
23
+ - Native filestore implementation
24
+ - UDP as default transport layer (optional ZMQ)
25
+
26
+ ## Installation
27
+
28
+ Clone the repository and then install via pip:
29
+
30
+ ```
31
+ pip install cfdp
32
+ ```
33
+
34
+ > Depending on the transport layer to use with CFDP, one needs to install additional dependencies.
35
+
36
+ ## Documentation
37
+
38
+ Find the detailed documentation [here](docs/README.md).
39
+
40
+ ## Tests
41
+
42
+ The protocol is tested for cross-support as outlined in CCSDS CFDP Yellow Book.
43
+ See [tests/README.md](tests/README.md) for details.
44
+
45
+ ## Contribute
46
+
47
+ To learn more on how to successfully contribute please read the contributing
48
+ information in the [LibreCube documentation](https://librecube.gitlab.io/).
49
+
50
+ ## Support
51
+
52
+ If you are having issues, please let us know. Reach us at
53
+ [Matrix](https://app.element.io/#/room/#librecube.org:matrix.org)
54
+ or via [Email](mailto:info@librecube.org).
55
+
56
+ ## License
57
+
58
+ The project is licensed under the MIT license. See the [LICENSE](./LICENSE.txt) file for details.
@@ -0,0 +1,68 @@
1
+ [project]
2
+ name = "pycfdp"
3
+ version = "0.2.2"
4
+ requires-python = ">=3.11"
5
+ authors = [
6
+ { name = "Alex Pope", email = "alexpopester@gmail.com" },
7
+ { name = "LibreCube (Original Codebase)", email = "info@librecube.org" },
8
+ ]
9
+ description = "Python implementation of CCSDS File Delivery Protocol (Modernized Fork)"
10
+ readme = "README.md"
11
+ license = { text = "MIT License" }
12
+ dependencies = []
13
+
14
+ [project.optional-dependencies]
15
+ zmq = ["pyzmq"]
16
+ spacepacket = ["spacepacket"]
17
+ all = ["pyzmq", "spacepacket"]
18
+
19
+ [build-system]
20
+ requires = ["setuptools>=68"]
21
+ build-backend = "setuptools.build_meta"
22
+
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
25
+
26
+ [dependency-groups]
27
+ dev = [
28
+ "pytest>=8",
29
+ "pyzmq",
30
+ "spacepacket",
31
+ "ruff>=0.16",
32
+ "mypy>=1.11",
33
+ ]
34
+
35
+ [tool.pytest.ini_options]
36
+ testpaths = ["tests"]
37
+ pythonpath = ["tests"]
38
+
39
+ [tool.mypy]
40
+ # Permissive by design (issue: finish what py.typed started). Default mypy does
41
+ # not check the bodies of un-annotated functions, so this gate validates the
42
+ # annotations that ARE present (the machine/shell seam) without demanding
43
+ # annotations across the whole codebase. Tighten incrementally as annotations
44
+ # spread (e.g. enable check_untyped_defs, then disallow_untyped_defs per-module).
45
+ python_version = "3.11"
46
+ files = ["src/cfdp"]
47
+
48
+ [[tool.mypy.overrides]]
49
+ # Optional transport deps ship no type stubs / py.typed marker.
50
+ module = ["spacepacket.*", "zmq.*"]
51
+ ignore_missing_imports = true
52
+
53
+ [tool.ruff]
54
+ target-version = "py311"
55
+ src = ["src", "tests"]
56
+
57
+ [tool.ruff.lint]
58
+ # Correctness (F), pycodestyle (E), import sorting (I), pyupgrade (UP).
59
+ select = ["E", "F", "I", "UP"]
60
+ ignore = [
61
+ "E501", # line length is owned by the formatter
62
+ "UP031", # legacy %-formatting in logging calls; harmless, deferred
63
+ ]
64
+
65
+ [tool.ruff.lint.per-file-ignores]
66
+ # __init__.py deliberately re-exports the public API (F401/F403) and sets up
67
+ # the package logger before imports (E402).
68
+ "__init__.py" = ["F401", "F403", "E402"]
pycfdp-0.2.2/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ ]
@@ -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
@@ -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
+ }