tftp 0.0.0__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.
- tftp-0.0.0/.gitignore +31 -0
- tftp-0.0.0/CHANGELOG.md +101 -0
- tftp-0.0.0/LICENSE +21 -0
- tftp-0.0.0/PKG-INFO +260 -0
- tftp-0.0.0/README.md +218 -0
- tftp-0.0.0/RELEASENOTES.md +70 -0
- tftp-0.0.0/docs/api/aio.md +7 -0
- tftp-0.0.0/docs/api/backends.md +9 -0
- tftp-0.0.0/docs/api/capture.md +13 -0
- tftp-0.0.0/docs/api/client.md +3 -0
- tftp-0.0.0/docs/api/errors.md +5 -0
- tftp-0.0.0/docs/api/netascii.md +3 -0
- tftp-0.0.0/docs/api/options.md +13 -0
- tftp-0.0.0/docs/api/packet.md +3 -0
- tftp-0.0.0/docs/api/path.md +5 -0
- tftp-0.0.0/docs/api/relay.md +7 -0
- tftp-0.0.0/docs/api/server.md +7 -0
- tftp-0.0.0/docs/api/transfer.md +3 -0
- tftp-0.0.0/docs/api/uri.md +3 -0
- tftp-0.0.0/docs/async.md +48 -0
- tftp-0.0.0/docs/backends.md +52 -0
- tftp-0.0.0/docs/capture.md +64 -0
- tftp-0.0.0/docs/changelog.md +1 -0
- tftp-0.0.0/docs/index.md +55 -0
- tftp-0.0.0/docs/paths.md +36 -0
- tftp-0.0.0/docs/protocol.md +96 -0
- tftp-0.0.0/docs/relay.md +61 -0
- tftp-0.0.0/docs/serving.md +124 -0
- tftp-0.0.0/examples/dynamic_boot_menu.py +35 -0
- tftp-0.0.0/examples/fetch.py +21 -0
- tftp-0.0.0/examples/serve_directory.py +20 -0
- tftp-0.0.0/mkdocs.yml +71 -0
- tftp-0.0.0/pyproject.toml +90 -0
- tftp-0.0.0/src/tftp/AGENTS.md +761 -0
- tftp-0.0.0/src/tftp/__init__.py +180 -0
- tftp-0.0.0/src/tftp/__main__.py +6 -0
- tftp-0.0.0/src/tftp/_sockets.py +36 -0
- tftp-0.0.0/src/tftp/aio/__init__.py +22 -0
- tftp-0.0.0/src/tftp/aio/bridge.py +214 -0
- tftp-0.0.0/src/tftp/aio/client.py +426 -0
- tftp-0.0.0/src/tftp/aio/server.py +314 -0
- tftp-0.0.0/src/tftp/backends/__init__.py +19 -0
- tftp-0.0.0/src/tftp/backends/http.py +142 -0
- tftp-0.0.0/src/tftp/backends/memory.py +93 -0
- tftp-0.0.0/src/tftp/backends/pipe.py +192 -0
- tftp-0.0.0/src/tftp/backends/proxy.py +162 -0
- tftp-0.0.0/src/tftp/capture/__init__.py +74 -0
- tftp-0.0.0/src/tftp/capture/events.py +161 -0
- tftp-0.0.0/src/tftp/capture/filters.py +149 -0
- tftp-0.0.0/src/tftp/capture/flows.py +290 -0
- tftp-0.0.0/src/tftp/capture/frames.py +189 -0
- tftp-0.0.0/src/tftp/capture/live.py +54 -0
- tftp-0.0.0/src/tftp/capture/pcap.py +225 -0
- tftp-0.0.0/src/tftp/cli/__init__.py +56 -0
- tftp-0.0.0/src/tftp/cli/capture.py +130 -0
- tftp-0.0.0/src/tftp/cli/common.py +240 -0
- tftp-0.0.0/src/tftp/cli/handlers.py +147 -0
- tftp-0.0.0/src/tftp/cli/relay.py +117 -0
- tftp-0.0.0/src/tftp/cli/serve.py +237 -0
- tftp-0.0.0/src/tftp/cli/transfer.py +174 -0
- tftp-0.0.0/src/tftp/client.py +769 -0
- tftp-0.0.0/src/tftp/errors.py +205 -0
- tftp-0.0.0/src/tftp/listing.py +123 -0
- tftp-0.0.0/src/tftp/netascii.py +149 -0
- tftp-0.0.0/src/tftp/options/__init__.py +89 -0
- tftp-0.0.0/src/tftp/options/base.py +175 -0
- tftp-0.0.0/src/tftp/options/builtin.py +318 -0
- tftp-0.0.0/src/tftp/options/negotiate.py +134 -0
- tftp-0.0.0/src/tftp/options/policy.py +96 -0
- tftp-0.0.0/src/tftp/options/profiles.py +74 -0
- tftp-0.0.0/src/tftp/options/registry.py +64 -0
- tftp-0.0.0/src/tftp/packet/__init__.py +40 -0
- tftp-0.0.0/src/tftp/packet/codec.py +194 -0
- tftp-0.0.0/src/tftp/packet/enums.py +32 -0
- tftp-0.0.0/src/tftp/path/__init__.py +34 -0
- tftp-0.0.0/src/tftp/path/_stream.py +195 -0
- tftp-0.0.0/src/tftp/path/local.py +221 -0
- tftp-0.0.0/src/tftp/path/uri.py +94 -0
- tftp-0.0.0/src/tftp/py.typed +0 -0
- tftp-0.0.0/src/tftp/relay/__init__.py +24 -0
- tftp-0.0.0/src/tftp/relay/core.py +429 -0
- tftp-0.0.0/src/tftp/relay/routing.py +148 -0
- tftp-0.0.0/src/tftp/relay/session.py +165 -0
- tftp-0.0.0/src/tftp/result.py +88 -0
- tftp-0.0.0/src/tftp/server/__init__.py +20 -0
- tftp-0.0.0/src/tftp/server/base.py +321 -0
- tftp-0.0.0/src/tftp/server/core.py +430 -0
- tftp-0.0.0/src/tftp/server/handler.py +290 -0
- tftp-0.0.0/src/tftp/server/listener.py +198 -0
- tftp-0.0.0/src/tftp/server/policy.py +63 -0
- tftp-0.0.0/src/tftp/server/session.py +320 -0
- tftp-0.0.0/src/tftp/server/stats.py +63 -0
- tftp-0.0.0/src/tftp/transfer/__init__.py +31 -0
- tftp-0.0.0/src/tftp/transfer/base.py +241 -0
- tftp-0.0.0/src/tftp/transfer/receiver.py +212 -0
- tftp-0.0.0/src/tftp/transfer/sender.py +175 -0
- tftp-0.0.0/src/tftp/uri.py +95 -0
- tftp-0.0.0/tests/conftest.py +70 -0
- tftp-0.0.0/tests/firmware_boot.py +228 -0
- tftp-0.0.0/tests/smoke_installed.py +45 -0
- tftp-0.0.0/tests/test_addresses.py +162 -0
- tftp-0.0.0/tests/test_aio.py +308 -0
- tftp-0.0.0/tests/test_backends.py +216 -0
- tftp-0.0.0/tests/test_capture.py +293 -0
- tftp-0.0.0/tests/test_cli.py +273 -0
- tftp-0.0.0/tests/test_client_server.py +358 -0
- tftp-0.0.0/tests/test_deploy.py +162 -0
- tftp-0.0.0/tests/test_engine.py +286 -0
- tftp-0.0.0/tests/test_interop.py +57 -0
- tftp-0.0.0/tests/test_interop_peers.py +228 -0
- tftp-0.0.0/tests/test_listing.py +139 -0
- tftp-0.0.0/tests/test_netascii.py +64 -0
- tftp-0.0.0/tests/test_option_registry.py +198 -0
- tftp-0.0.0/tests/test_options.py +110 -0
- tftp-0.0.0/tests/test_packet.py +91 -0
- tftp-0.0.0/tests/test_path.py +120 -0
- tftp-0.0.0/tests/test_protocol.py +278 -0
- tftp-0.0.0/tests/test_relay.py +261 -0
- tftp-0.0.0/tests/test_robustness.py +494 -0
- tftp-0.0.0/tests/test_uri.py +65 -0
tftp-0.0.0/.gitignore
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Dotfiles are local by default; re-include the ones that are shared.
|
|
2
|
+
/.*
|
|
3
|
+
!/.gitignore
|
|
4
|
+
!/.gitattributes
|
|
5
|
+
!/.github/
|
|
6
|
+
|
|
7
|
+
# Agent configs / private notes -- never tracked in this repo.
|
|
8
|
+
# `.agents` is deliberately slashless: it is usually a symlink, which a
|
|
9
|
+
# directory-only `.agents/` pattern would not match.
|
|
10
|
+
.agents
|
|
11
|
+
*.local.*
|
|
12
|
+
CLAUDE*
|
|
13
|
+
.claude
|
|
14
|
+
|
|
15
|
+
# Python
|
|
16
|
+
__pycache__/
|
|
17
|
+
*.py[cod]
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.hypothesis/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
.coverage*
|
|
23
|
+
htmlcov/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.venv/
|
|
26
|
+
.pyvenv/
|
|
27
|
+
|
|
28
|
+
# Build / docs output
|
|
29
|
+
build/
|
|
30
|
+
dist/
|
|
31
|
+
site/
|
tftp-0.0.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.0.0] - 2026-10-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `Client`: download and upload over IPv4 and IPv6, to and from paths, binary
|
|
15
|
+
files or memory, with `blksize`, `windowsize`, `tsize` and `timeout`
|
|
16
|
+
negotiation (and the `utimeout`, `rollover`, `blksize2` and `cookie`
|
|
17
|
+
extensions on request), progress and `on_negotiated` callbacks, exponential
|
|
18
|
+
backoff of retransmissions, an optional time limit, and a one-time retry
|
|
19
|
+
without options when a server refuses them (ERROR 8). `blksize="mtu"` sizes
|
|
20
|
+
blocks to the route's interface MTU; hosts may carry a port
|
|
21
|
+
(`"[::1]:6969"`).
|
|
22
|
+
- `Server`: one event-loop thread serving any number of transfers, each on its
|
|
23
|
+
own socket bound to the address the request was sent to (pktinfo); dual-stack
|
|
24
|
+
`::` listening by default; a negotiation policy (`ServerOptions`: maximum
|
|
25
|
+
block and window size, window memory bound, allowed and refused options,
|
|
26
|
+
fitting `blksize` to the arrival interface's MTU); resource limits
|
|
27
|
+
(`ServerLimits`: request size, filename length, option count and length,
|
|
28
|
+
sessions per client, transfer duration); ignoring requests sent to broadcast
|
|
29
|
+
or multicast addresses; blocking handlers opened in worker threads;
|
|
30
|
+
dallying on the last ACK of an upload; `on_complete` for every transfer;
|
|
31
|
+
counters for metrics (`stats`, `stats_snapshot()`).
|
|
32
|
+
- `FileSystemHandler`: serves a directory with containment against `..`,
|
|
33
|
+
symlinks and Windows device names; backslash separators; atomic uploads;
|
|
34
|
+
`create`/`overwrite` policy; `max_upload` and free-space checks from `tsize`.
|
|
35
|
+
- Handler protocol (`open_read`/`open_write`) for generated content; sources
|
|
36
|
+
and sinks may report "not ready yet" (`WouldBlock`) and wake the transfer
|
|
37
|
+
from another thread, so slow backends apply backpressure instead of
|
|
38
|
+
blocking the server.
|
|
39
|
+
- Backends: `MemoryHandler`; `HttpHandler`, a TFTP-to-HTTP(S) gateway (GET and
|
|
40
|
+
PUT, HTTP status codes mapped to TFTP errors, `Content-Length` as `tsize`);
|
|
41
|
+
`UpstreamHandler`, a terminating proxy to another TFTP server whose two sides
|
|
42
|
+
negotiate independently; `Pipe` for writing such handlers.
|
|
43
|
+
- `Relay`: a transparent relay forwarding requests byte for byte to upstream
|
|
44
|
+
servers and datagrams unchanged between the two transfer IDs, with routing
|
|
45
|
+
by client subnet, filename prefix or arrival interface, idle and lifetime
|
|
46
|
+
limits, per-transfer summaries and counters.
|
|
47
|
+
- `tftp.aio`: `AsyncClient` (including `stream()`, an async generator with
|
|
48
|
+
backpressure) and `AsyncServer` (async handlers and streams, pktinfo on every
|
|
49
|
+
event loop including Windows' Proactor loop).
|
|
50
|
+
- Option negotiation through a registry of option handlers, so applications can
|
|
51
|
+
add their own; compatibility profiles `STRICT`, `DEFAULT`, `PXE`, `HPA` and
|
|
52
|
+
`LEGACY`.
|
|
53
|
+
- Capture and debugging: `trace=` hooks on the client, server and relay
|
|
54
|
+
reporting every datagram as a `PacketEvent`; `PcapWriter` (Wireshark-readable
|
|
55
|
+
pcap); pcap/pcapng reading from files or live pipes with IP fragment
|
|
56
|
+
reassembly; `FlowTracker`/`analyze()` reconstructing transfers and their
|
|
57
|
+
files from a capture; a filter language; live capture on Linux.
|
|
58
|
+
- `tftp://` URLs (RFC 3617): `parse_url`, `format_url`, `download_url`,
|
|
59
|
+
`upload_url`.
|
|
60
|
+
- `Client.size()`: a file's size without transferring it (a `tsize` probe the
|
|
61
|
+
server counts as declined, not failed).
|
|
62
|
+
- `path` extra: `TftpPath`, a pathlib-next `Path` bound to a client
|
|
63
|
+
(`client.path(...)`), and `TftpUriPath`, the `tftp://` scheme for
|
|
64
|
+
pathlib-next's `UriPath`; whole-file streaming reads and writes, `stat()`
|
|
65
|
+
without a transfer, `copy()`/`move()` across schemes.
|
|
66
|
+
- Typed errors: `RemoteError` raised as `FileNotFound`, `AccessViolation`,
|
|
67
|
+
`DiskFull`, `IllegalOperation`, `UnknownTransferId`, `FileAlreadyExists`,
|
|
68
|
+
`NoSuchUser` or `OptionNegotiationError`; `TransferAborted`.
|
|
69
|
+
- Transfer engine (`Sender`/`Receiver`) with RFC 7440 windowing, the
|
|
70
|
+
Sorcerer's Apprentice fix, unknown-TID handling, tolerance of repeated
|
|
71
|
+
OACKs and unlimited file size via block-number rollover, usable without
|
|
72
|
+
sockets.
|
|
73
|
+
- Streaming netascii in both directions; packet encode/decode for every RFC
|
|
74
|
+
1350 and RFC 2347 packet, keeping a request's raw bytes for forwarding.
|
|
75
|
+
- `pytftp` command (`cli` extra): `get` and `put` (also by URL), `serve` (a
|
|
76
|
+
directory, `--http` gateway or `--upstream` proxy), `relay`, `capture`;
|
|
77
|
+
`--compat` profiles, `--trace` and `--pcap` on every command that moves
|
|
78
|
+
packets, `--json` output.
|
|
79
|
+
- `port_range=` on `Server`, `AsyncServer` and `Relay` (`PortRange`):
|
|
80
|
+
transfer sockets take their ports from a range a firewall can allow;
|
|
81
|
+
`--port-range` on `pytftp serve` and `relay`.
|
|
82
|
+
- `pytftp serve --per-client` (serve `ROOT/<client address>/` when it exists),
|
|
83
|
+
`--ignore-case` and `--remap REGEX=REPLACEMENT`.
|
|
84
|
+
- `mstfwindow`, Windows bootmgr's variable-window option, as an extension a
|
|
85
|
+
server may allow (fixed window of 4).
|
|
86
|
+
- pytftp's `x-list` and `x-mtime` extensions (`LISTING_OPTIONS`): directory
|
|
87
|
+
listings (`tftp.listing`) and modification times. `Client.stat()`,
|
|
88
|
+
`Client.listdir()` (async too), `pytftp ls`, `pytftp serve --listing`, and
|
|
89
|
+
`iterdir`/`walk`/`glob`/`is_dir`/`st_mtime` on `TftpPath` and `TftpUriPath`.
|
|
90
|
+
- Hosts, networks and interfaces accepted as objects (`ipaddress` types,
|
|
91
|
+
`netimps.Host`, `netimps.Interface`) wherever an address is taken, typed
|
|
92
|
+
with netimps' aliases.
|
|
93
|
+
- `interface=` on `Server`, `AsyncServer` and `Relay` (and `--interface` on
|
|
94
|
+
`pytftp serve`/`relay`): listen on one network adapter, by name,
|
|
95
|
+
`netimps.Interface`, MAC or address.
|
|
96
|
+
- `RequestContext.interface`: the arrival interface as a `netimps.Interface`.
|
|
97
|
+
- Loopback throughput benchmark (`benchmarks/run.py`).
|
|
98
|
+
- Licensed under MIT.
|
|
99
|
+
|
|
100
|
+
[Unreleased]: https://github.com/jose-pr/pytftp/compare/v0.0.0...HEAD
|
|
101
|
+
[0.0.0]: https://github.com/jose-pr/pytftp/releases/tag/v0.0.0
|
tftp-0.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jose-pr
|
|
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.
|
tftp-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tftp
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Pure-Python TFTP client and server for IPv4 and IPv6: RFC 1350, 2347, 2348, 2349 and 7440
|
|
5
|
+
Project-URL: Homepage, https://github.com/jose-pr/pytftp/
|
|
6
|
+
Project-URL: Documentation, https://jose-pr.github.io/pytftp/
|
|
7
|
+
Project-URL: Issues, https://github.com/jose-pr/pytftp/issues
|
|
8
|
+
Author-email: jose-pr <jose-pr@coqui.dev>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Internet
|
|
22
|
+
Classifier: Topic :: System :: Boot
|
|
23
|
+
Classifier: Topic :: System :: Networking
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: netimps<0.4,>=0.3.4
|
|
27
|
+
Provides-Extra: cli
|
|
28
|
+
Requires-Dist: duho<0.7,>=0.6.0; extra == 'cli'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: black; extra == 'dev'
|
|
31
|
+
Requires-Dist: build; extra == 'dev'
|
|
32
|
+
Requires-Dist: duho<0.7,>=0.6.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pathlib-next[uri]<0.10,>=0.9.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: mkdocs; extra == 'docs'
|
|
37
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
38
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
39
|
+
Provides-Extra: path
|
|
40
|
+
Requires-Dist: pathlib-next[uri]<0.10,>=0.9.0; extra == 'path'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# tftp
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/tftp/)
|
|
46
|
+
[](https://pypi.org/project/tftp/)
|
|
47
|
+
[](https://github.com/jose-pr/pytftp/blob/main/LICENSE)
|
|
48
|
+
[](https://jose-pr.github.io/pytftp/)
|
|
49
|
+
[](https://github.com/jose-pr/pytftp/actions/workflows/test.yml)
|
|
50
|
+
|
|
51
|
+
A **pure-Python TFTP client, server, relay and capture decoder for IPv4 and
|
|
52
|
+
IPv6** that implements the whole of modern TFTP: RFC 1350, the option
|
|
53
|
+
extension (RFC 2347) with `blksize`, `timeout`, `tsize` and `windowsize`
|
|
54
|
+
(RFC 2348, 2349, 7440), tftp-hpa's extensions, block-number rollover and
|
|
55
|
+
netascii. Built as infrastructure for network boot: it interoperates with
|
|
56
|
+
iPXE, tftp-hpa, BusyBox, dnsmasq and curl, serves files, HTTP or another TFTP
|
|
57
|
+
server, and shows you what crossed the wire.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import tftp
|
|
61
|
+
|
|
62
|
+
tftp.Client("192.0.2.1").download("pxelinux.0", "pxelinux.0")
|
|
63
|
+
|
|
64
|
+
with tftp.Server("/srv/tftp") as server: # IPv6 + IPv4, port 69
|
|
65
|
+
server.serve_forever()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Features
|
|
69
|
+
|
|
70
|
+
- **Complete option support** — `blksize` up to 65464, RFC 7440 windows,
|
|
71
|
+
`tsize` both ways, `timeout`, tftp-hpa's `utimeout`, `rollover`,
|
|
72
|
+
`blksize2`, `cookie`, and Windows bootmgr's `mstfwindow`. Unlimited file size. A registry for your own options,
|
|
73
|
+
and profiles (`pxe`, `hpa`, `legacy`, `strict`) for quirky peers.
|
|
74
|
+
- **Fast** — windowed transfers move hundreds of MiB/s over loopback in pure
|
|
75
|
+
Python (see [benchmarks](https://github.com/jose-pr/pytftp/blob/main/benchmarks/README.md)); the hot path reads into
|
|
76
|
+
preallocated buffers and keeps a sent window in memory instead of re-reading.
|
|
77
|
+
- **Correct under loss** — Sorcerer's Apprentice fix, exponential backoff,
|
|
78
|
+
gap detection inside a window, repeated OACKs tolerated, ERROR 5 for stray
|
|
79
|
+
packets without disturbing the transfer, dallying on the last ACK. The
|
|
80
|
+
engine is tested over a simulated lossy link and fuzzed.
|
|
81
|
+
- **IPv4 and IPv6** — dual-stack listening by default; v4 clients are served
|
|
82
|
+
from plain v4 sockets.
|
|
83
|
+
- **Replies from the request's address** — on a multi-homed host or a VIP,
|
|
84
|
+
each transfer answers from the address the request was sent to (pktinfo,
|
|
85
|
+
via [netimps](https://github.com/jose-pr/netimps)); tested on Linux and
|
|
86
|
+
Windows.
|
|
87
|
+
- **Safe file serving** — no path escapes `root` (`..`, symlinks, Windows
|
|
88
|
+
device names), uploads are atomic (a failed upload leaves nothing), quotas
|
|
89
|
+
and disk-space checks from `tsize`.
|
|
90
|
+
- **Pluggable backends** — a handler is two methods, so boot menus or images
|
|
91
|
+
can be generated per client; built in: a directory, memory, an HTTP(S)
|
|
92
|
+
gateway, and a terminating proxy to another TFTP server. Slow backends apply
|
|
93
|
+
backpressure instead of blocking other transfers.
|
|
94
|
+
- **Transparent relay** — forwards requests byte for byte to upstream servers
|
|
95
|
+
(routed by subnet, filename or interface), so unknown extensions still work.
|
|
96
|
+
- **Capture and debugging** — trace every datagram from the client, server or
|
|
97
|
+
relay; write Wireshark-readable pcaps; read pcap/pcapng files or a live
|
|
98
|
+
`tcpdump` pipe, reconstruct each transfer and extract its file.
|
|
99
|
+
- **Directory listings** — an opt-in extension between pytftp peers: `pytftp ls`,
|
|
100
|
+
`Client.listdir()`, and `iterdir`/`walk`/`glob` on paths; other servers
|
|
101
|
+
simply ignore it.
|
|
102
|
+
- **Deployment** — transfer ports pinned to a range for firewalls; per-client
|
|
103
|
+
roots, case-insensitive names and filename remapping in `pytftp serve`.
|
|
104
|
+
- **asyncio** — `AsyncClient` and `AsyncServer` with async handlers and streams.
|
|
105
|
+
- **pathlib** — `client.path("boot/x").read_bytes()`, and `tftp://` URLs in
|
|
106
|
+
[pathlib-next](https://github.com/jose-pr/pathlib-next), so
|
|
107
|
+
`UriPath("tftp://h/x").copy("s3://bucket/x")` just works (optional extra).
|
|
108
|
+
- **Bounded** — limits on requests, sessions per client, window memory and
|
|
109
|
+
transfer time; counters for metrics.
|
|
110
|
+
- **CLI** — `pytftp get|put|serve|relay|capture` with `--json`, `--trace` and
|
|
111
|
+
`--pcap` (optional extra).
|
|
112
|
+
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pip install tftp # library
|
|
117
|
+
pip install "tftp[cli]" # plus the pytftp command
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
| Extra | Adds | Needed for |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `cli` | `duho` | the `pytftp` command and `python -m tftp` |
|
|
123
|
+
| `path` | `pathlib-next[uri]` | `TftpPath`, and `tftp://` URLs in `pathlib_next.uri.UriPath` |
|
|
124
|
+
|
|
125
|
+
Requires Python 3.9+. The one required dependency, `netimps`, has no
|
|
126
|
+
dependencies of its own.
|
|
127
|
+
|
|
128
|
+
## Quick start
|
|
129
|
+
|
|
130
|
+
Client:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
import tftp
|
|
134
|
+
|
|
135
|
+
client = tftp.Client("boot.example.net", blksize=1428, windowsize=16)
|
|
136
|
+
result = client.download("images/vmlinuz", "vmlinuz")
|
|
137
|
+
print(result.bytes, result.duration, result.negotiated)
|
|
138
|
+
|
|
139
|
+
data = client.get("pxelinux.cfg/default") # into memory
|
|
140
|
+
client.upload("logs/boot.txt", b"ok\n") # needs a writable server
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Server with uploads, a bounded window and a completion hook:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import tftp
|
|
147
|
+
|
|
148
|
+
server = tftp.Server(
|
|
149
|
+
"/srv/tftp",
|
|
150
|
+
"::",
|
|
151
|
+
69,
|
|
152
|
+
writable=True,
|
|
153
|
+
options=tftp.ServerOptions(max_blksize=8192, max_windowsize=32),
|
|
154
|
+
on_complete=lambda r: print(r.operation, r.filename, r.peer[0], r.error or "ok"),
|
|
155
|
+
)
|
|
156
|
+
server.serve_forever()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Generated content:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import io
|
|
163
|
+
import tftp
|
|
164
|
+
|
|
165
|
+
class Menu(tftp.FileSystemHandler):
|
|
166
|
+
def open_read(self, context):
|
|
167
|
+
if context.filename == "menu.cfg":
|
|
168
|
+
return io.BytesIO(b"client %s\n" % context.peer[0].encode())
|
|
169
|
+
return super().open_read(context)
|
|
170
|
+
|
|
171
|
+
tftp.Server(Menu("/srv/tftp")).serve_forever()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Command line:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pytftp get 192.0.2.1 pxelinux.0 -b 1428 -w 16
|
|
178
|
+
pytftp put 192.0.2.1 firmware.bin
|
|
179
|
+
pytftp serve /srv/tftp --port 6969 --write --json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Relay, capture and asyncio:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
import tftp
|
|
186
|
+
from tftp.relay import Relay, RouteTable, by_subnet
|
|
187
|
+
from tftp.capture import PcapWriter, analyze
|
|
188
|
+
|
|
189
|
+
# Forward requests to per-subnet boot servers, recording everything.
|
|
190
|
+
with PcapWriter("relay.pcap") as pcap:
|
|
191
|
+
route = RouteTable([by_subnet({"10.1.0.0/16": "10.1.0.5"})], default="10.0.0.20")
|
|
192
|
+
Relay(route, trace=pcap).serve_forever()
|
|
193
|
+
|
|
194
|
+
# What happened in a capture (yours, or tcpdump's)?
|
|
195
|
+
for transfer in analyze("boot.pcapng").transfers:
|
|
196
|
+
print(transfer, transfer.missing_blocks)
|
|
197
|
+
|
|
198
|
+
# asyncio
|
|
199
|
+
async def fetch():
|
|
200
|
+
async for chunk in tftp.aio.AsyncClient("192.0.2.1").stream("vmlinuz"):
|
|
201
|
+
...
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
pytftp serve --http https://images.example.com/pxe/ --compat pxe
|
|
206
|
+
pytftp relay 10.0.0.20 --route-prefix windows/=wds.lan --trace
|
|
207
|
+
tcpdump -i eth0 -U -w - udp | pytftp capture - --filter "op=RRQ,ERROR"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## API overview
|
|
211
|
+
|
|
212
|
+
| Module | Purpose |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `tftp.client` | `Client`, `download`, `upload` |
|
|
215
|
+
| `tftp.server` | `Server`, `ServerLimits`, `FileSystemHandler`, `AtomicWriter`, `RequestContext` |
|
|
216
|
+
| `tftp.backends` | `MemoryHandler`, `HttpHandler`, `UpstreamHandler` (proxy), `Pipe` |
|
|
217
|
+
| `tftp.relay` | `Relay` and routing helpers |
|
|
218
|
+
| `tftp.aio` | `AsyncClient`, `AsyncServer` |
|
|
219
|
+
| `tftp.capture` | trace events, `PcapWriter`, pcap/pcapng reading, `analyze`, filters |
|
|
220
|
+
| `tftp.options` | `ServerOptions`, option registry, profiles, `Negotiated` |
|
|
221
|
+
| `tftp.packet` | `Opcode`, `ErrorCode`, packet types, `encode_*`/`decode` |
|
|
222
|
+
| `tftp.transfer` | I/O-free `Sender`/`Receiver` engine |
|
|
223
|
+
| `tftp.uri` | `tftp://` URLs |
|
|
224
|
+
| `tftp.path` | `TftpPath`, `TftpUriPath` (`path` extra) |
|
|
225
|
+
| `tftp.netascii` | streaming netascii translation |
|
|
226
|
+
| `tftp.errors` | `TftpError` and the typed `RemoteError` subclasses |
|
|
227
|
+
| `tftp.cli` | the `pytftp` command (`cli` extra) |
|
|
228
|
+
|
|
229
|
+
Everything is also importable from `tftp` directly. The complete reference,
|
|
230
|
+
with every signature and gotcha, is [`src/tftp/AGENTS.md`](https://github.com/jose-pr/pytftp/blob/main/src/tftp/AGENTS.md),
|
|
231
|
+
which ships inside the package.
|
|
232
|
+
|
|
233
|
+
## Development
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
py -3.14 -m venv .venv/3.14-nt-arm64 # or python3.14 -m venv .venv/3.14-posix-x86_64
|
|
237
|
+
.venv/3.14-nt-arm64/Scripts/pip install -e ".[dev]"
|
|
238
|
+
.venv/3.14-nt-arm64/Scripts/python -m pytest -q
|
|
239
|
+
.venv/3.14-nt-arm64/Scripts/python -m black --check src tests benchmarks examples
|
|
240
|
+
python benchmarks/run.py
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`pytest -m "not slow"` skips the rollover tests; tests marked `interop` run
|
|
244
|
+
against curl, tftp-hpa, BusyBox and dnsmasq where installed (their servers need
|
|
245
|
+
passwordless `sudo`). `sudo python tests/firmware_boot.py ipxe` network-boots
|
|
246
|
+
iPXE in QEMU against the server (its `uefi` scenario does not pass yet). See
|
|
247
|
+
[AGENTS.md](https://github.com/jose-pr/pytftp/blob/main/AGENTS.md) for the layout and conventions.
|
|
248
|
+
|
|
249
|
+
### Releasing
|
|
250
|
+
|
|
251
|
+
This project follows [Semantic Versioning](https://semver.org/) and keeps a
|
|
252
|
+
[`CHANGELOG.md`](https://github.com/jose-pr/pytftp/blob/main/CHANGELOG.md). Pushing a tag matching `v*` triggers the release
|
|
253
|
+
workflow: test gate → build (checking the tag names the version built) → a strict
|
|
254
|
+
docs build as a gate → GitHub release → publish. The release workflow never deploys
|
|
255
|
+
the docs site itself: for a final release its last job dispatches the docs workflow
|
|
256
|
+
at the tag, which owns every Pages deploy.
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT — see [LICENSE](https://github.com/jose-pr/pytftp/blob/main/LICENSE).
|
tftp-0.0.0/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# tftp
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/tftp/)
|
|
4
|
+
[](https://pypi.org/project/tftp/)
|
|
5
|
+
[](https://github.com/jose-pr/pytftp/blob/main/LICENSE)
|
|
6
|
+
[](https://jose-pr.github.io/pytftp/)
|
|
7
|
+
[](https://github.com/jose-pr/pytftp/actions/workflows/test.yml)
|
|
8
|
+
|
|
9
|
+
A **pure-Python TFTP client, server, relay and capture decoder for IPv4 and
|
|
10
|
+
IPv6** that implements the whole of modern TFTP: RFC 1350, the option
|
|
11
|
+
extension (RFC 2347) with `blksize`, `timeout`, `tsize` and `windowsize`
|
|
12
|
+
(RFC 2348, 2349, 7440), tftp-hpa's extensions, block-number rollover and
|
|
13
|
+
netascii. Built as infrastructure for network boot: it interoperates with
|
|
14
|
+
iPXE, tftp-hpa, BusyBox, dnsmasq and curl, serves files, HTTP or another TFTP
|
|
15
|
+
server, and shows you what crossed the wire.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import tftp
|
|
19
|
+
|
|
20
|
+
tftp.Client("192.0.2.1").download("pxelinux.0", "pxelinux.0")
|
|
21
|
+
|
|
22
|
+
with tftp.Server("/srv/tftp") as server: # IPv6 + IPv4, port 69
|
|
23
|
+
server.serve_forever()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- **Complete option support** — `blksize` up to 65464, RFC 7440 windows,
|
|
29
|
+
`tsize` both ways, `timeout`, tftp-hpa's `utimeout`, `rollover`,
|
|
30
|
+
`blksize2`, `cookie`, and Windows bootmgr's `mstfwindow`. Unlimited file size. A registry for your own options,
|
|
31
|
+
and profiles (`pxe`, `hpa`, `legacy`, `strict`) for quirky peers.
|
|
32
|
+
- **Fast** — windowed transfers move hundreds of MiB/s over loopback in pure
|
|
33
|
+
Python (see [benchmarks](https://github.com/jose-pr/pytftp/blob/main/benchmarks/README.md)); the hot path reads into
|
|
34
|
+
preallocated buffers and keeps a sent window in memory instead of re-reading.
|
|
35
|
+
- **Correct under loss** — Sorcerer's Apprentice fix, exponential backoff,
|
|
36
|
+
gap detection inside a window, repeated OACKs tolerated, ERROR 5 for stray
|
|
37
|
+
packets without disturbing the transfer, dallying on the last ACK. The
|
|
38
|
+
engine is tested over a simulated lossy link and fuzzed.
|
|
39
|
+
- **IPv4 and IPv6** — dual-stack listening by default; v4 clients are served
|
|
40
|
+
from plain v4 sockets.
|
|
41
|
+
- **Replies from the request's address** — on a multi-homed host or a VIP,
|
|
42
|
+
each transfer answers from the address the request was sent to (pktinfo,
|
|
43
|
+
via [netimps](https://github.com/jose-pr/netimps)); tested on Linux and
|
|
44
|
+
Windows.
|
|
45
|
+
- **Safe file serving** — no path escapes `root` (`..`, symlinks, Windows
|
|
46
|
+
device names), uploads are atomic (a failed upload leaves nothing), quotas
|
|
47
|
+
and disk-space checks from `tsize`.
|
|
48
|
+
- **Pluggable backends** — a handler is two methods, so boot menus or images
|
|
49
|
+
can be generated per client; built in: a directory, memory, an HTTP(S)
|
|
50
|
+
gateway, and a terminating proxy to another TFTP server. Slow backends apply
|
|
51
|
+
backpressure instead of blocking other transfers.
|
|
52
|
+
- **Transparent relay** — forwards requests byte for byte to upstream servers
|
|
53
|
+
(routed by subnet, filename or interface), so unknown extensions still work.
|
|
54
|
+
- **Capture and debugging** — trace every datagram from the client, server or
|
|
55
|
+
relay; write Wireshark-readable pcaps; read pcap/pcapng files or a live
|
|
56
|
+
`tcpdump` pipe, reconstruct each transfer and extract its file.
|
|
57
|
+
- **Directory listings** — an opt-in extension between pytftp peers: `pytftp ls`,
|
|
58
|
+
`Client.listdir()`, and `iterdir`/`walk`/`glob` on paths; other servers
|
|
59
|
+
simply ignore it.
|
|
60
|
+
- **Deployment** — transfer ports pinned to a range for firewalls; per-client
|
|
61
|
+
roots, case-insensitive names and filename remapping in `pytftp serve`.
|
|
62
|
+
- **asyncio** — `AsyncClient` and `AsyncServer` with async handlers and streams.
|
|
63
|
+
- **pathlib** — `client.path("boot/x").read_bytes()`, and `tftp://` URLs in
|
|
64
|
+
[pathlib-next](https://github.com/jose-pr/pathlib-next), so
|
|
65
|
+
`UriPath("tftp://h/x").copy("s3://bucket/x")` just works (optional extra).
|
|
66
|
+
- **Bounded** — limits on requests, sessions per client, window memory and
|
|
67
|
+
transfer time; counters for metrics.
|
|
68
|
+
- **CLI** — `pytftp get|put|serve|relay|capture` with `--json`, `--trace` and
|
|
69
|
+
`--pcap` (optional extra).
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install tftp # library
|
|
75
|
+
pip install "tftp[cli]" # plus the pytftp command
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Extra | Adds | Needed for |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `cli` | `duho` | the `pytftp` command and `python -m tftp` |
|
|
81
|
+
| `path` | `pathlib-next[uri]` | `TftpPath`, and `tftp://` URLs in `pathlib_next.uri.UriPath` |
|
|
82
|
+
|
|
83
|
+
Requires Python 3.9+. The one required dependency, `netimps`, has no
|
|
84
|
+
dependencies of its own.
|
|
85
|
+
|
|
86
|
+
## Quick start
|
|
87
|
+
|
|
88
|
+
Client:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import tftp
|
|
92
|
+
|
|
93
|
+
client = tftp.Client("boot.example.net", blksize=1428, windowsize=16)
|
|
94
|
+
result = client.download("images/vmlinuz", "vmlinuz")
|
|
95
|
+
print(result.bytes, result.duration, result.negotiated)
|
|
96
|
+
|
|
97
|
+
data = client.get("pxelinux.cfg/default") # into memory
|
|
98
|
+
client.upload("logs/boot.txt", b"ok\n") # needs a writable server
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Server with uploads, a bounded window and a completion hook:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import tftp
|
|
105
|
+
|
|
106
|
+
server = tftp.Server(
|
|
107
|
+
"/srv/tftp",
|
|
108
|
+
"::",
|
|
109
|
+
69,
|
|
110
|
+
writable=True,
|
|
111
|
+
options=tftp.ServerOptions(max_blksize=8192, max_windowsize=32),
|
|
112
|
+
on_complete=lambda r: print(r.operation, r.filename, r.peer[0], r.error or "ok"),
|
|
113
|
+
)
|
|
114
|
+
server.serve_forever()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Generated content:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
import io
|
|
121
|
+
import tftp
|
|
122
|
+
|
|
123
|
+
class Menu(tftp.FileSystemHandler):
|
|
124
|
+
def open_read(self, context):
|
|
125
|
+
if context.filename == "menu.cfg":
|
|
126
|
+
return io.BytesIO(b"client %s\n" % context.peer[0].encode())
|
|
127
|
+
return super().open_read(context)
|
|
128
|
+
|
|
129
|
+
tftp.Server(Menu("/srv/tftp")).serve_forever()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Command line:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pytftp get 192.0.2.1 pxelinux.0 -b 1428 -w 16
|
|
136
|
+
pytftp put 192.0.2.1 firmware.bin
|
|
137
|
+
pytftp serve /srv/tftp --port 6969 --write --json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Relay, capture and asyncio:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import tftp
|
|
144
|
+
from tftp.relay import Relay, RouteTable, by_subnet
|
|
145
|
+
from tftp.capture import PcapWriter, analyze
|
|
146
|
+
|
|
147
|
+
# Forward requests to per-subnet boot servers, recording everything.
|
|
148
|
+
with PcapWriter("relay.pcap") as pcap:
|
|
149
|
+
route = RouteTable([by_subnet({"10.1.0.0/16": "10.1.0.5"})], default="10.0.0.20")
|
|
150
|
+
Relay(route, trace=pcap).serve_forever()
|
|
151
|
+
|
|
152
|
+
# What happened in a capture (yours, or tcpdump's)?
|
|
153
|
+
for transfer in analyze("boot.pcapng").transfers:
|
|
154
|
+
print(transfer, transfer.missing_blocks)
|
|
155
|
+
|
|
156
|
+
# asyncio
|
|
157
|
+
async def fetch():
|
|
158
|
+
async for chunk in tftp.aio.AsyncClient("192.0.2.1").stream("vmlinuz"):
|
|
159
|
+
...
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pytftp serve --http https://images.example.com/pxe/ --compat pxe
|
|
164
|
+
pytftp relay 10.0.0.20 --route-prefix windows/=wds.lan --trace
|
|
165
|
+
tcpdump -i eth0 -U -w - udp | pytftp capture - --filter "op=RRQ,ERROR"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## API overview
|
|
169
|
+
|
|
170
|
+
| Module | Purpose |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `tftp.client` | `Client`, `download`, `upload` |
|
|
173
|
+
| `tftp.server` | `Server`, `ServerLimits`, `FileSystemHandler`, `AtomicWriter`, `RequestContext` |
|
|
174
|
+
| `tftp.backends` | `MemoryHandler`, `HttpHandler`, `UpstreamHandler` (proxy), `Pipe` |
|
|
175
|
+
| `tftp.relay` | `Relay` and routing helpers |
|
|
176
|
+
| `tftp.aio` | `AsyncClient`, `AsyncServer` |
|
|
177
|
+
| `tftp.capture` | trace events, `PcapWriter`, pcap/pcapng reading, `analyze`, filters |
|
|
178
|
+
| `tftp.options` | `ServerOptions`, option registry, profiles, `Negotiated` |
|
|
179
|
+
| `tftp.packet` | `Opcode`, `ErrorCode`, packet types, `encode_*`/`decode` |
|
|
180
|
+
| `tftp.transfer` | I/O-free `Sender`/`Receiver` engine |
|
|
181
|
+
| `tftp.uri` | `tftp://` URLs |
|
|
182
|
+
| `tftp.path` | `TftpPath`, `TftpUriPath` (`path` extra) |
|
|
183
|
+
| `tftp.netascii` | streaming netascii translation |
|
|
184
|
+
| `tftp.errors` | `TftpError` and the typed `RemoteError` subclasses |
|
|
185
|
+
| `tftp.cli` | the `pytftp` command (`cli` extra) |
|
|
186
|
+
|
|
187
|
+
Everything is also importable from `tftp` directly. The complete reference,
|
|
188
|
+
with every signature and gotcha, is [`src/tftp/AGENTS.md`](https://github.com/jose-pr/pytftp/blob/main/src/tftp/AGENTS.md),
|
|
189
|
+
which ships inside the package.
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
py -3.14 -m venv .venv/3.14-nt-arm64 # or python3.14 -m venv .venv/3.14-posix-x86_64
|
|
195
|
+
.venv/3.14-nt-arm64/Scripts/pip install -e ".[dev]"
|
|
196
|
+
.venv/3.14-nt-arm64/Scripts/python -m pytest -q
|
|
197
|
+
.venv/3.14-nt-arm64/Scripts/python -m black --check src tests benchmarks examples
|
|
198
|
+
python benchmarks/run.py
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`pytest -m "not slow"` skips the rollover tests; tests marked `interop` run
|
|
202
|
+
against curl, tftp-hpa, BusyBox and dnsmasq where installed (their servers need
|
|
203
|
+
passwordless `sudo`). `sudo python tests/firmware_boot.py ipxe` network-boots
|
|
204
|
+
iPXE in QEMU against the server (its `uefi` scenario does not pass yet). See
|
|
205
|
+
[AGENTS.md](https://github.com/jose-pr/pytftp/blob/main/AGENTS.md) for the layout and conventions.
|
|
206
|
+
|
|
207
|
+
### Releasing
|
|
208
|
+
|
|
209
|
+
This project follows [Semantic Versioning](https://semver.org/) and keeps a
|
|
210
|
+
[`CHANGELOG.md`](https://github.com/jose-pr/pytftp/blob/main/CHANGELOG.md). Pushing a tag matching `v*` triggers the release
|
|
211
|
+
workflow: test gate → build (checking the tag names the version built) → a strict
|
|
212
|
+
docs build as a gate → GitHub release → publish. The release workflow never deploys
|
|
213
|
+
the docs site itself: for a final release its last job dispatches the docs workflow
|
|
214
|
+
at the tag, which owns every Pages deploy.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT — see [LICENSE](https://github.com/jose-pr/pytftp/blob/main/LICENSE).
|