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.
- ccsds_ethernet_client-0.2.1/.github/workflows/ci.yml +52 -0
- ccsds_ethernet_client-0.2.1/.github/workflows/release.yml +124 -0
- ccsds_ethernet_client-0.2.1/.gitignore +8 -0
- ccsds_ethernet_client-0.2.1/AGENTS.md +8 -0
- ccsds_ethernet_client-0.2.1/CONTRIBUTING.md +14 -0
- ccsds_ethernet_client-0.2.1/Cargo.lock +133 -0
- ccsds_ethernet_client-0.2.1/Cargo.toml +23 -0
- ccsds_ethernet_client-0.2.1/DEPENDENCIES.md +16 -0
- ccsds_ethernet_client-0.2.1/LICENSE +21 -0
- ccsds_ethernet_client-0.2.1/PKG-INFO +134 -0
- ccsds_ethernet_client-0.2.1/README.md +115 -0
- ccsds_ethernet_client-0.2.1/docs/agent-operating-loop.md +105 -0
- ccsds_ethernet_client-0.2.1/docs/release.md +120 -0
- ccsds_ethernet_client-0.2.1/justfile +29 -0
- ccsds_ethernet_client-0.2.1/pyproject.toml +31 -0
- ccsds_ethernet_client-0.2.1/python/ccsds_ethernet_client/__init__.py +25 -0
- ccsds_ethernet_client-0.2.1/python/ccsds_ethernet_client/_native.pyi +101 -0
- ccsds_ethernet_client-0.2.1/python/ccsds_ethernet_client/py.typed +1 -0
- ccsds_ethernet_client-0.2.1/python/tests/test_bindings.py +86 -0
- ccsds_ethernet_client-0.2.1/scripts/cut_release.py +232 -0
- ccsds_ethernet_client-0.2.1/scripts/test_cut_release.py +387 -0
- ccsds_ethernet_client-0.2.1/scripts/test_release_workflow.py +43 -0
- ccsds_ethernet_client-0.2.1/src/endpoint.rs +82 -0
- ccsds_ethernet_client-0.2.1/src/ethernet.rs +629 -0
- ccsds_ethernet_client-0.2.1/src/lib.rs +66 -0
- ccsds_ethernet_client-0.2.1/src/linux.rs +1074 -0
- ccsds_ethernet_client-0.2.1/src/python.rs +506 -0
- ccsds_ethernet_client-0.2.1/src/session.rs +284 -0
- ccsds_ethernet_client-0.2.1/src/transport.rs +283 -0
- ccsds_ethernet_client-0.2.1/tests/frame_transport.rs +487 -0
- 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
|
+
# 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.
|