ccsds-ethernet-client 0.2.1__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 (31) hide show
  1. ccsds_ethernet_client-0.2.1/.github/workflows/ci.yml +52 -0
  2. ccsds_ethernet_client-0.2.1/.github/workflows/release.yml +124 -0
  3. ccsds_ethernet_client-0.2.1/.gitignore +8 -0
  4. ccsds_ethernet_client-0.2.1/AGENTS.md +8 -0
  5. ccsds_ethernet_client-0.2.1/CONTRIBUTING.md +14 -0
  6. ccsds_ethernet_client-0.2.1/Cargo.lock +133 -0
  7. ccsds_ethernet_client-0.2.1/Cargo.toml +23 -0
  8. ccsds_ethernet_client-0.2.1/DEPENDENCIES.md +16 -0
  9. ccsds_ethernet_client-0.2.1/LICENSE +21 -0
  10. ccsds_ethernet_client-0.2.1/PKG-INFO +134 -0
  11. ccsds_ethernet_client-0.2.1/README.md +115 -0
  12. ccsds_ethernet_client-0.2.1/docs/agent-operating-loop.md +105 -0
  13. ccsds_ethernet_client-0.2.1/docs/release.md +120 -0
  14. ccsds_ethernet_client-0.2.1/justfile +29 -0
  15. ccsds_ethernet_client-0.2.1/pyproject.toml +31 -0
  16. ccsds_ethernet_client-0.2.1/python/ccsds_ethernet_client/__init__.py +25 -0
  17. ccsds_ethernet_client-0.2.1/python/ccsds_ethernet_client/_native.pyi +101 -0
  18. ccsds_ethernet_client-0.2.1/python/ccsds_ethernet_client/py.typed +1 -0
  19. ccsds_ethernet_client-0.2.1/python/tests/test_bindings.py +86 -0
  20. ccsds_ethernet_client-0.2.1/scripts/cut_release.py +232 -0
  21. ccsds_ethernet_client-0.2.1/scripts/test_cut_release.py +387 -0
  22. ccsds_ethernet_client-0.2.1/scripts/test_release_workflow.py +43 -0
  23. ccsds_ethernet_client-0.2.1/src/endpoint.rs +82 -0
  24. ccsds_ethernet_client-0.2.1/src/ethernet.rs +629 -0
  25. ccsds_ethernet_client-0.2.1/src/lib.rs +66 -0
  26. ccsds_ethernet_client-0.2.1/src/linux.rs +1074 -0
  27. ccsds_ethernet_client-0.2.1/src/python.rs +506 -0
  28. ccsds_ethernet_client-0.2.1/src/session.rs +284 -0
  29. ccsds_ethernet_client-0.2.1/src/transport.rs +283 -0
  30. ccsds_ethernet_client-0.2.1/tests/frame_transport.rs +487 -0
  31. ccsds_ethernet_client-0.2.1/tests/session.rs +306 -0
@@ -0,0 +1,52 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+
9
+ env:
10
+ CARGO_TERM_COLOR: always
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ ci:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
20
+ with:
21
+ persist-credentials: false
22
+ - name: Use Rust stable
23
+ run: rustup update stable && rustup default stable
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
26
+ - name: Install just
27
+ uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
28
+ with:
29
+ just-version: "1.51.0"
30
+ - name: Check formatting
31
+ run: cargo fmt --all -- --check
32
+ - name: Run clippy
33
+ run: cargo clippy --locked --all-targets -- -D warnings
34
+ - name: Run tests
35
+ run: cargo test --locked
36
+ - name: Check Python bindings
37
+ run: |
38
+ PYO3_NO_PYTHON=1 cargo clippy --locked --features python --all-targets -- -D warnings
39
+ uv run --python 3.11 --isolated --no-project --with maturin==1.11.5 bash -euc '
40
+ maturin develop --locked
41
+ python -m unittest discover -s python/tests -v
42
+ '
43
+ - name: Run release contract tests
44
+ run: |
45
+ uv run scripts/test_cut_release.py
46
+ uv run scripts/test_release_workflow.py
47
+ - name: Check documentation
48
+ run: PYO3_NO_PYTHON=1 RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps
49
+ - name: Verify package
50
+ run: cargo package --locked
51
+ - name: Build project
52
+ run: cargo build --release
@@ -0,0 +1,124 @@
1
+ name: Release packages
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ env:
11
+ CARGO_TERM_COLOR: always
12
+
13
+ jobs:
14
+ verify:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
18
+ with:
19
+ persist-credentials: false
20
+ - name: Use Rust stable
21
+ run: rustup update stable && rustup default stable
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
24
+ - name: Install just
25
+ uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
26
+ with:
27
+ just-version: "1.51.0"
28
+ - name: Verify release tag and packages
29
+ env:
30
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
31
+ run: |
32
+ test "$RELEASE_TAG" = "v$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
33
+ cargo fmt --check
34
+ cargo test --locked
35
+ cargo clippy --locked --all-targets -- -D warnings
36
+ PYO3_NO_PYTHON=1 cargo clippy --locked --features python --all-targets -- -D warnings
37
+ uv run --python 3.11 --isolated --no-project --with maturin==1.11.5 bash -euc '
38
+ maturin develop --locked
39
+ python -m unittest discover -s python/tests -v
40
+ '
41
+ uv run scripts/test_cut_release.py
42
+ uv run scripts/test_release_workflow.py
43
+ cargo package --locked
44
+
45
+ build-python-wheels:
46
+ needs: verify
47
+ runs-on: ubuntu-latest
48
+ strategy:
49
+ fail-fast: false
50
+ matrix:
51
+ target:
52
+ - x86_64-unknown-linux-gnu
53
+ - aarch64-unknown-linux-gnu
54
+ steps:
55
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
56
+ with:
57
+ persist-credentials: false
58
+ - name: Build Python wheel
59
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
60
+ with:
61
+ command: build
62
+ target: ${{ matrix.target }}
63
+ manylinux: "2_28"
64
+ maturin-version: v1.11.5
65
+ args: --release --locked --compatibility pypi --out dist
66
+ - name: Store Python wheel
67
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
68
+ with:
69
+ name: python-wheel-${{ matrix.target }}
70
+ path: dist/*.whl
71
+ if-no-files-found: error
72
+
73
+ build-python-sdist:
74
+ needs: verify
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
78
+ with:
79
+ persist-credentials: false
80
+ - name: Build Python source distribution
81
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
82
+ with:
83
+ command: sdist
84
+ maturin-version: v1.11.5
85
+ args: --out dist
86
+ - name: Store Python source distribution
87
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
88
+ with:
89
+ name: python-sdist
90
+ path: dist/*.tar.gz
91
+ if-no-files-found: error
92
+
93
+ publish-crate:
94
+ needs: [verify, build-python-wheels, build-python-sdist]
95
+ runs-on: ubuntu-latest
96
+ environment: crates-io
97
+ steps:
98
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
99
+ with:
100
+ persist-credentials: false
101
+ - name: Use Rust stable
102
+ run: rustup update stable && rustup default stable
103
+ - name: Publish crate
104
+ run: cargo publish --locked
105
+ env:
106
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
107
+
108
+ publish-pypi:
109
+ needs: publish-crate
110
+ runs-on: ubuntu-latest
111
+ environment:
112
+ name: pypi
113
+ url: https://pypi.org/p/ccsds-ethernet-client
114
+ permissions:
115
+ id-token: write
116
+ steps:
117
+ - name: Retrieve Python distributions
118
+ uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
119
+ with:
120
+ pattern: python-*
121
+ path: dist
122
+ merge-multiple: true
123
+ - name: Publish Python distributions
124
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,8 @@
1
+ /target/
2
+ /.vscode/
3
+ /.venv/
4
+ /dist/
5
+ *.egg-info/
6
+ __pycache__/
7
+ *.so
8
+ *.pyd
@@ -0,0 +1,8 @@
1
+ # Repository instructions
2
+
3
+ - Development and live transport use are Linux-led. Run `just check` for validation.
4
+ - GitHub Actions are allowed for this public repository. Never install or invoke TruffleHog in this repository.
5
+ - Rust and Python releases share the `Cargo.toml` version and SemVer tags of the form `vMAJOR.MINOR.PATCH`. For ordinary release execution, use the `cut-release` skill and the checked-in `just cut-release` runner. GitHub Actions builds all artifacts, publishes crates.io first, and then publishes PyPI.
6
+ - Generic shared implementation must not define consumer intent or safety policy.
7
+ - Before changing behavior, read `docs/agent-operating-loop.md` and preserve its invariants unless the PR explicitly changes the public contract.
8
+ - Accrete future agent knowledge into the nearest executable test or durable doc. Avoid one-off plans that do not update `README.md`, `docs/`, `AGENTS.md`, or the release runner.
@@ -0,0 +1,14 @@
1
+ # Contributing
2
+
3
+ Contributions are welcome.
4
+
5
+ Development is Linux-led and uses a recent stable Rust toolchain plus
6
+ [`just`](https://github.com/casey/just).
7
+
8
+ Before opening a pull request, run:
9
+
10
+ ```sh
11
+ just check
12
+ ```
13
+
14
+ Keep changes focused, and update tests or documentation when behavior changes.
@@ -0,0 +1,133 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "ccsds-ethernet-client"
7
+ version = "0.2.1"
8
+ dependencies = [
9
+ "libc",
10
+ "pyo3",
11
+ ]
12
+
13
+ [[package]]
14
+ name = "heck"
15
+ version = "0.5.0"
16
+ source = "registry+https://github.com/rust-lang/crates.io-index"
17
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
18
+
19
+ [[package]]
20
+ name = "libc"
21
+ version = "0.2.189"
22
+ source = "registry+https://github.com/rust-lang/crates.io-index"
23
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
24
+
25
+ [[package]]
26
+ name = "once_cell"
27
+ version = "1.21.4"
28
+ source = "registry+https://github.com/rust-lang/crates.io-index"
29
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
30
+
31
+ [[package]]
32
+ name = "portable-atomic"
33
+ version = "1.15.0"
34
+ source = "registry+https://github.com/rust-lang/crates.io-index"
35
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
36
+
37
+ [[package]]
38
+ name = "proc-macro2"
39
+ version = "1.0.107"
40
+ source = "registry+https://github.com/rust-lang/crates.io-index"
41
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
42
+ dependencies = [
43
+ "unicode-ident",
44
+ ]
45
+
46
+ [[package]]
47
+ name = "pyo3"
48
+ version = "0.29.2"
49
+ source = "registry+https://github.com/rust-lang/crates.io-index"
50
+ checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
51
+ dependencies = [
52
+ "libc",
53
+ "once_cell",
54
+ "portable-atomic",
55
+ "pyo3-build-config",
56
+ "pyo3-ffi",
57
+ "pyo3-macros",
58
+ ]
59
+
60
+ [[package]]
61
+ name = "pyo3-build-config"
62
+ version = "0.29.2"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
65
+ dependencies = [
66
+ "target-lexicon",
67
+ ]
68
+
69
+ [[package]]
70
+ name = "pyo3-ffi"
71
+ version = "0.29.2"
72
+ source = "registry+https://github.com/rust-lang/crates.io-index"
73
+ checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
74
+ dependencies = [
75
+ "libc",
76
+ "pyo3-build-config",
77
+ ]
78
+
79
+ [[package]]
80
+ name = "pyo3-macros"
81
+ version = "0.29.2"
82
+ source = "registry+https://github.com/rust-lang/crates.io-index"
83
+ checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
84
+ dependencies = [
85
+ "proc-macro2",
86
+ "pyo3-macros-backend",
87
+ "quote",
88
+ "syn",
89
+ ]
90
+
91
+ [[package]]
92
+ name = "pyo3-macros-backend"
93
+ version = "0.29.2"
94
+ source = "registry+https://github.com/rust-lang/crates.io-index"
95
+ checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
96
+ dependencies = [
97
+ "heck",
98
+ "proc-macro2",
99
+ "quote",
100
+ "syn",
101
+ ]
102
+
103
+ [[package]]
104
+ name = "quote"
105
+ version = "1.0.47"
106
+ source = "registry+https://github.com/rust-lang/crates.io-index"
107
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
108
+ dependencies = [
109
+ "proc-macro2",
110
+ ]
111
+
112
+ [[package]]
113
+ name = "syn"
114
+ version = "2.0.119"
115
+ source = "registry+https://github.com/rust-lang/crates.io-index"
116
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
117
+ dependencies = [
118
+ "proc-macro2",
119
+ "quote",
120
+ "unicode-ident",
121
+ ]
122
+
123
+ [[package]]
124
+ name = "target-lexicon"
125
+ version = "0.13.5"
126
+ source = "registry+https://github.com/rust-lang/crates.io-index"
127
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
128
+
129
+ [[package]]
130
+ name = "unicode-ident"
131
+ version = "1.0.24"
132
+ source = "registry+https://github.com/rust-lang/crates.io-index"
133
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
@@ -0,0 +1,23 @@
1
+ [package]
2
+ name = "ccsds-ethernet-client"
3
+ version = "0.2.1"
4
+ edition = "2024"
5
+ rust-version = "1.85"
6
+ authors = ["Ian Cleary <iancleary@hey.com>"]
7
+ description = "Typed in-process CCSDS raw-Ethernet client"
8
+ license = "MIT"
9
+ readme = "README.md"
10
+ repository = "https://github.com/iancleary/ccsds-ethernet-client"
11
+ publish = true
12
+
13
+ [lib]
14
+ crate-type = ["rlib", "cdylib"]
15
+
16
+ [features]
17
+ python = ["dep:pyo3", "pyo3/extension-module"]
18
+
19
+ [dependencies]
20
+ pyo3 = { version = "0.29.2", optional = true, features = ["abi3-py311"] }
21
+
22
+ [target.'cfg(target_os = "linux")'.dependencies]
23
+ libc = "0.2.189"
@@ -0,0 +1,16 @@
1
+ # Dependency inventory
2
+
3
+ The default Rust library and tests use only the Rust standard library. Linux
4
+ builds use the locked `libc` crate for the isolated `AF_PACKET` system-call
5
+ boundary. The optional `python` feature uses the locked PyO3 dependency and
6
+ its procedural-macro dependencies. Live raw-socket access requires
7
+ deployment-managed `CAP_NET_RAW`; that capability is not needed by the
8
+ hardware-free test suite. `Cargo.lock` is the authoritative resolved-version
9
+ inventory and remains checked in.
10
+
11
+ The Python extension is built with Maturin 1.11.5. The standalone release
12
+ runner and release workflow tests use only the Python standard library and run
13
+ with `uv`; local release execution also uses `git`, `just`, and the GitHub CLI.
14
+ These tools are not Rust runtime dependencies. No async runtime, pcap/libpcap,
15
+ serialization, CLI, RPC, service, database, or recording dependency is
16
+ included.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ian Cleary
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccsds-ethernet-client
3
+ Version: 0.2.1
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: License :: OSI Approved :: MIT License
6
+ Classifier: Operating System :: POSIX :: Linux
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Rust
9
+ Classifier: Topic :: System :: Networking
10
+ License-File: LICENSE
11
+ Summary: Python bindings for the typed CCSDS raw-Ethernet client
12
+ Author-email: Ian Cleary <iancleary@hey.com>
13
+ License-Expression: MIT
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
16
+ Project-URL: Issues, https://github.com/iancleary/ccsds-ethernet-client/issues
17
+ Project-URL: Repository, https://github.com/iancleary/ccsds-ethernet-client
18
+
19
+ # CCSDS Ethernet client
20
+
21
+ This project provides typed, in-process command exchange with a directly
22
+ attached CCSDS Ethernet endpoint. The Rust crate owns the full typed session
23
+ contract. The optional Python package exposes strict configuration, byte-level
24
+ frame helpers, and the Linux raw transport for test orchestrators. It has no
25
+ daemon, service, RPC API, sender CLI, plugin system, recording layer,
26
+ automatic retry, or UDP/raw fallback.
27
+
28
+ It is available under the [MIT License](LICENSE).
29
+
30
+ ## Contract
31
+
32
+ `RawEthernetConfig` and its endpoints are immutable after strict construction.
33
+ The config requires schema version `2`, caller-supplied concrete unicast IPv4
34
+ or IPv6 endpoints, nonzero UDP ports, unicast nonzero MAC addresses, matching
35
+ IP address families, distinct host/board identity, a valid Linux interface
36
+ name, and a nonzero packet-ring capacity. The crate never infers, discovers,
37
+ selects, rewrites, or learns peer addresses.
38
+
39
+ A mission crate implements `Codec` with its own typed `Command`,
40
+ `Acknowledgement`, `Telemetry`, and opaque `Correlation`. `Session::open`
41
+ (Linux) or `Session::from_transport` starts receive before any command can be
42
+ sent. `exchange_once` accepts an absolute monotonic deadline and performs one
43
+ send only. An already-expired deadline is `DeadlineExpiredBeforeSend`; a
44
+ receive failure after send is `DeliveryOutcomeUnknown`. Matching busy or
45
+ rejected acknowledgements remain successful typed decode results for the
46
+ codec/caller to interpret. `next_telemetry` returns telemetry observed while an
47
+ exchange waited for its matching acknowledgement. `close` is idempotent.
48
+
49
+ The Linux transport is cfg-gated and uses an interface-bound `AF_PACKET` raw
50
+ socket. Live use requires Linux and `CAP_NET_RAW`; granting that capability and
51
+ selecting an interface are deployment responsibilities. Frame/config/ring/
52
+ session tests use `MemoryTransport`; default checks never open a NIC or require
53
+ root.
54
+
55
+ ## Python package
56
+
57
+ Install `ccsds-ethernet-client` from PyPI on Python 3.11 or newer. Published
58
+ wheels target Linux x86-64 and AArch64. The source distribution supports other
59
+ Linux targets with a Rust toolchain. Frame helpers also build on other
60
+ platforms, but `RawEthernetClient` rejects live use outside Linux.
61
+
62
+ ```python
63
+ from ccsds_ethernet_client import RawEthernetClient, RawEthernetConfig
64
+
65
+ config = RawEthernetConfig(
66
+ interface_name="eth0",
67
+ host_mac="02:00:00:00:00:01",
68
+ host_ip="169.254.209.1",
69
+ host_udp_port=49152,
70
+ board_mac="02:00:00:00:00:7a",
71
+ board_ip="169.254.209.0",
72
+ board_udp_port=24576,
73
+ ring_capacity=64,
74
+ )
75
+
76
+ with RawEthernetClient(config) as client:
77
+ client.send(b"\x10\x01")
78
+ datagram = client.receive(timeout_seconds=0.5)
79
+ print(datagram.payload, datagram.sender_ip)
80
+ ```
81
+
82
+ `receive` raises `TimeoutError` when its relative monotonic timeout expires.
83
+ Configuration failures raise `ConfigError`. Frame construction and parsing
84
+ failures raise `FrameError`. Both are `ValueError` subclasses. Other live
85
+ transport failures raise `TransportError`. The package does not encode
86
+ commands, correlate acknowledgements, retry, or interpret telemetry. The test
87
+ orchestrator owns those policies.
88
+
89
+ `TransportStatistics` reports transport observations only. Detailed frame
90
+ classification counters describe what the receive path observed; they do not
91
+ decide whether a consumer should accept, reject, persist, or invalidate an
92
+ exchange, recording, test run, or safety case. Unsupported EtherTypes continue
93
+ to contribute to `ignored_non_ipv4_frames` and also receive a factual detailed
94
+ counter. Endpoint mismatches contribute to `foreign_frames`. Parse and
95
+ integrity failures contribute to `invalid_frames` and, when the reason is
96
+ recognized, the matching detailed failure counter.
97
+
98
+ Ignored frame payload bytes are not retained. Consumers own serialization,
99
+ recording schemas, mission interpretation, and safety policy.
100
+
101
+ ## Checks
102
+
103
+ ```sh
104
+ just check
105
+ ```
106
+
107
+ Use `just python-test` to run only the extension build and hardware-free Python
108
+ contract tests.
109
+
110
+ ## Maintenance
111
+
112
+ Future agent and maintainer workflow guidance lives in
113
+ [`docs/agent-operating-loop.md`](docs/agent-operating-loop.md). Use it to keep
114
+ changes aligned with the crate's transport-only boundary and executable test
115
+ contracts.
116
+
117
+ ## Scope boundary
118
+
119
+ Mission APIDs, secondary headers, request-ID allocation, acknowledgement
120
+ status policy, retries, recording schemas, logic, decisions, intent, and safety
121
+ policy belong to consuming systems.
122
+
123
+ This crate defines transport and exchange mechanics only; it does not define
124
+ subsystem intent or safety policy. Generic file recording is not part of this
125
+ crate. Schema version `2` uses a standard 1500-byte Ethernet MTU. IPv4 UDP
126
+ payloads are limited to 1472 bytes (1500 minus the 20-byte IPv4 and 8-byte UDP
127
+ headers). IPv6 UDP payloads are limited to 1452 bytes (1500 minus the 40-byte
128
+ IPv6 and 8-byte UDP headers). Use
129
+ `RawEthernetConfig::maximum_udp_payload_bytes()` for the configured family.
130
+
131
+ See [`docs/release.md`](docs/release.md) for the local release process.
132
+ See [`docs/agent-operating-loop.md`](docs/agent-operating-loop.md) for the
133
+ agent-facing transport boundary.
134
+
@@ -0,0 +1,115 @@
1
+ # CCSDS Ethernet client
2
+
3
+ This project provides typed, in-process command exchange with a directly
4
+ attached CCSDS Ethernet endpoint. The Rust crate owns the full typed session
5
+ contract. The optional Python package exposes strict configuration, byte-level
6
+ frame helpers, and the Linux raw transport for test orchestrators. It has no
7
+ daemon, service, RPC API, sender CLI, plugin system, recording layer,
8
+ automatic retry, or UDP/raw fallback.
9
+
10
+ It is available under the [MIT License](LICENSE).
11
+
12
+ ## Contract
13
+
14
+ `RawEthernetConfig` and its endpoints are immutable after strict construction.
15
+ The config requires schema version `2`, caller-supplied concrete unicast IPv4
16
+ or IPv6 endpoints, nonzero UDP ports, unicast nonzero MAC addresses, matching
17
+ IP address families, distinct host/board identity, a valid Linux interface
18
+ name, and a nonzero packet-ring capacity. The crate never infers, discovers,
19
+ selects, rewrites, or learns peer addresses.
20
+
21
+ A mission crate implements `Codec` with its own typed `Command`,
22
+ `Acknowledgement`, `Telemetry`, and opaque `Correlation`. `Session::open`
23
+ (Linux) or `Session::from_transport` starts receive before any command can be
24
+ sent. `exchange_once` accepts an absolute monotonic deadline and performs one
25
+ send only. An already-expired deadline is `DeadlineExpiredBeforeSend`; a
26
+ receive failure after send is `DeliveryOutcomeUnknown`. Matching busy or
27
+ rejected acknowledgements remain successful typed decode results for the
28
+ codec/caller to interpret. `next_telemetry` returns telemetry observed while an
29
+ exchange waited for its matching acknowledgement. `close` is idempotent.
30
+
31
+ The Linux transport is cfg-gated and uses an interface-bound `AF_PACKET` raw
32
+ socket. Live use requires Linux and `CAP_NET_RAW`; granting that capability and
33
+ selecting an interface are deployment responsibilities. Frame/config/ring/
34
+ session tests use `MemoryTransport`; default checks never open a NIC or require
35
+ root.
36
+
37
+ ## Python package
38
+
39
+ Install `ccsds-ethernet-client` from PyPI on Python 3.11 or newer. Published
40
+ wheels target Linux x86-64 and AArch64. The source distribution supports other
41
+ Linux targets with a Rust toolchain. Frame helpers also build on other
42
+ platforms, but `RawEthernetClient` rejects live use outside Linux.
43
+
44
+ ```python
45
+ from ccsds_ethernet_client import RawEthernetClient, RawEthernetConfig
46
+
47
+ config = RawEthernetConfig(
48
+ interface_name="eth0",
49
+ host_mac="02:00:00:00:00:01",
50
+ host_ip="169.254.209.1",
51
+ host_udp_port=49152,
52
+ board_mac="02:00:00:00:00:7a",
53
+ board_ip="169.254.209.0",
54
+ board_udp_port=24576,
55
+ ring_capacity=64,
56
+ )
57
+
58
+ with RawEthernetClient(config) as client:
59
+ client.send(b"\x10\x01")
60
+ datagram = client.receive(timeout_seconds=0.5)
61
+ print(datagram.payload, datagram.sender_ip)
62
+ ```
63
+
64
+ `receive` raises `TimeoutError` when its relative monotonic timeout expires.
65
+ Configuration failures raise `ConfigError`. Frame construction and parsing
66
+ failures raise `FrameError`. Both are `ValueError` subclasses. Other live
67
+ transport failures raise `TransportError`. The package does not encode
68
+ commands, correlate acknowledgements, retry, or interpret telemetry. The test
69
+ orchestrator owns those policies.
70
+
71
+ `TransportStatistics` reports transport observations only. Detailed frame
72
+ classification counters describe what the receive path observed; they do not
73
+ decide whether a consumer should accept, reject, persist, or invalidate an
74
+ exchange, recording, test run, or safety case. Unsupported EtherTypes continue
75
+ to contribute to `ignored_non_ipv4_frames` and also receive a factual detailed
76
+ counter. Endpoint mismatches contribute to `foreign_frames`. Parse and
77
+ integrity failures contribute to `invalid_frames` and, when the reason is
78
+ recognized, the matching detailed failure counter.
79
+
80
+ Ignored frame payload bytes are not retained. Consumers own serialization,
81
+ recording schemas, mission interpretation, and safety policy.
82
+
83
+ ## Checks
84
+
85
+ ```sh
86
+ just check
87
+ ```
88
+
89
+ Use `just python-test` to run only the extension build and hardware-free Python
90
+ contract tests.
91
+
92
+ ## Maintenance
93
+
94
+ Future agent and maintainer workflow guidance lives in
95
+ [`docs/agent-operating-loop.md`](docs/agent-operating-loop.md). Use it to keep
96
+ changes aligned with the crate's transport-only boundary and executable test
97
+ contracts.
98
+
99
+ ## Scope boundary
100
+
101
+ Mission APIDs, secondary headers, request-ID allocation, acknowledgement
102
+ status policy, retries, recording schemas, logic, decisions, intent, and safety
103
+ policy belong to consuming systems.
104
+
105
+ This crate defines transport and exchange mechanics only; it does not define
106
+ subsystem intent or safety policy. Generic file recording is not part of this
107
+ crate. Schema version `2` uses a standard 1500-byte Ethernet MTU. IPv4 UDP
108
+ payloads are limited to 1472 bytes (1500 minus the 20-byte IPv4 and 8-byte UDP
109
+ headers). IPv6 UDP payloads are limited to 1452 bytes (1500 minus the 40-byte
110
+ IPv6 and 8-byte UDP headers). Use
111
+ `RawEthernetConfig::maximum_udp_payload_bytes()` for the configured family.
112
+
113
+ See [`docs/release.md`](docs/release.md) for the local release process.
114
+ See [`docs/agent-operating-loop.md`](docs/agent-operating-loop.md) for the
115
+ agent-facing transport boundary.