iperf3-lib 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
iperf3_lib/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Top-level package for iperf3-lib: modern Python wrapper for iperf3 using cffi."""
2
+
3
+ from .config import ClientConfig, Protocol
4
+ from .iperf_client import Client
5
+ from .iperf_server import Server
6
+ from .result import Result
7
+
8
+ __all__ = ["Client", "ClientConfig", "Protocol", "Result", "Server"]
@@ -0,0 +1,28 @@
1
+ """Feature detection for optional libiperf capabilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .ffi import api
6
+
7
+
8
+ def has_symbol(name: str) -> bool:
9
+ """Check if the loaded libiperf exposes a given symbol.
10
+
11
+ Args:
12
+ name: The symbol name to check.
13
+
14
+ Returns:
15
+ True if the symbol exists, False otherwise.
16
+ """
17
+ try:
18
+ return hasattr(api.lib, name)
19
+ except Exception:
20
+ return False
21
+
22
+
23
+ # Common optional features
24
+ HAS_BIDIR = has_symbol("iperf_set_test_bidirectional")
25
+ HAS_MPTCP = has_symbol("iperf_set_test_mptcp")
26
+ HAS_JSON_OUTPUT = has_symbol("iperf_set_test_json_output")
27
+
28
+ __all__ = ["has_symbol", "HAS_BIDIR", "HAS_MPTCP", "HAS_JSON_OUTPUT"]
iperf3_lib/config.py ADDED
@@ -0,0 +1,33 @@
1
+ """Configuration models and enums for iperf3 client and server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+
7
+ from pydantic import BaseModel, Field, IPvAnyAddress, NonNegativeInt, PositiveInt
8
+
9
+
10
+ class Protocol(str, Enum):
11
+ """Supported protocols for iperf3 tests."""
12
+
13
+ TCP = "tcp"
14
+ UDP = "udp"
15
+ SCTP = "sctp" # requires kernel+lib support
16
+
17
+
18
+ class ClientConfig(BaseModel):
19
+ """Configuration for an iperf3 client run."""
20
+
21
+ server: str | IPvAnyAddress
22
+ port: PositiveInt = 5201
23
+ protocol: Protocol = Protocol.TCP
24
+ duration: PositiveInt = 10
25
+ parallel: PositiveInt = 1
26
+ omit: NonNegativeInt = 0
27
+ reverse: bool = False
28
+ bidirectional: bool = False # --bidir (>= 3.7)
29
+ mptcp: bool = False # (>= 3.19, Linux)
30
+ blksize: PositiveInt | None = None # --blksize
31
+ rate: int | None = None # numeric rate alias (bits/sec) - canonical for lib API
32
+ tos: int | None = Field(default=None, ge=0, le=255)
33
+ json_stream: bool = False # if lib supports streaming JSON chunks
@@ -0,0 +1,13 @@
1
+ """Custom exceptions for iperf3-lib errors and unsupported features."""
2
+
3
+
4
+ class IperfError(RuntimeError):
5
+ """libiperf reported a runtime error (i_errno/iperf_strerror)."""
6
+
7
+
8
+ class IperfLibraryError(RuntimeError):
9
+ """Failed to create or use libiperf objects (allocation/dlopen issues)."""
10
+
11
+
12
+ class UnsupportedFeatureError(RuntimeError):
13
+ """Requested feature is not supported by the loaded libiperf."""
@@ -0,0 +1 @@
1
+ """FFI subpackage for low-level libiperf bindings."""
iperf3_lib/ffi/api.py ADDED
@@ -0,0 +1,38 @@
1
+ """FFI bindings and dynamic loading for libiperf."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Final
7
+
8
+ from cffi import FFI
9
+
10
+ from .symbols import CDEF
11
+
12
+ POSSIBLE_NAMES: Final[tuple[str, ...]] = (
13
+ "libiperf.so",
14
+ "libiperf.so.0", # Linux common soname
15
+ "libiperf.dylib", # macOS
16
+ "iperf3.dll",
17
+ "libiperf.dll", # Windows (if available)
18
+ )
19
+
20
+ ffi = FFI()
21
+ ffi.cdef(CDEF)
22
+
23
+
24
+ def _dlopen():
25
+ # Prefer a clearer env var key for package consumers
26
+ path = os.getenv("IPERF3_LIB")
27
+ if path:
28
+ return ffi.dlopen(path)
29
+ last_err: Exception | None = None
30
+ for name in POSSIBLE_NAMES:
31
+ try:
32
+ return ffi.dlopen(name)
33
+ except OSError as e: # noqa: PERF203 - ok in a tiny loop
34
+ last_err = e
35
+ raise OSError(f"Could not load libiperf. Tried {POSSIBLE_NAMES}. Last error: {last_err}")
36
+
37
+
38
+ lib = _dlopen()
@@ -0,0 +1,38 @@
1
+ """CFFI symbol definitions for libiperf API."""
2
+
3
+ CDEF = r"""
4
+ typedef struct iperf_test iperf_test;
5
+
6
+ /* lifecycle */
7
+ iperf_test *iperf_new_test(void);
8
+ int iperf_defaults(iperf_test *t);
9
+ void iperf_free_test(iperf_test *t);
10
+
11
+ /* params */
12
+ void iperf_set_test_role(iperf_test *t, char role); /* 'c' or 's' */
13
+ void iperf_set_test_server_hostname(iperf_test *t, char *server_host);
14
+ void iperf_set_test_server_port(iperf_test *t, int port);
15
+ void iperf_set_test_duration(iperf_test *t, int seconds);
16
+ void iperf_set_test_num_streams(iperf_test *t, int n);
17
+ void iperf_set_test_blksize(iperf_test *t, int bytes);
18
+ void iperf_set_test_tos(iperf_test *t, int tos);
19
+ void iperf_set_test_omit(iperf_test *t, int seconds);
20
+ void iperf_set_test_reverse(iperf_test *t, int on);
21
+
22
+ /* optional / feature gated (may not exist on older libs) */
23
+ void iperf_set_test_json_output(iperf_test *t, int on);
24
+ void iperf_set_test_json_stream(iperf_test *t, int on);
25
+ void iperf_set_test_bidirectional(iperf_test *t, int on);
26
+ void iperf_set_test_json_stream_full_output(iperf_test *t, int on);
27
+ void iperf_set_test_rate(iperf_test *t, unsigned long long rate);
28
+
29
+ /* run/reset */
30
+ int iperf_run_client(iperf_test *t);
31
+ int iperf_run_server(iperf_test *t);
32
+ void iperf_reset_test(iperf_test *t);
33
+
34
+ /* output / errors */
35
+ char *iperf_get_test_json_output_string(iperf_test *t);
36
+ extern int i_errno;
37
+ char *iperf_strerror(int);
38
+ """
@@ -0,0 +1,148 @@
1
+ """Client wrapper and helpers for running iperf3 client tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from collections.abc import Callable
8
+ from functools import partial
9
+
10
+ from .config import ClientConfig, Protocol
11
+ from .exceptions import IperfError, IperfLibraryError, UnsupportedFeatureError
12
+ from .ffi.api import ffi, lib
13
+ from .result import EndStats, Result, SumStats
14
+
15
+
16
+ def _check(ret: int) -> None:
17
+ """Raise IperfError if the return code is negative."""
18
+ if ret < 0:
19
+ err = lib.iperf_strerror(lib.i_errno)
20
+ msg = ffi.string(err).decode() if err != ffi.NULL else "unknown libiperf error"
21
+ raise IperfError(msg)
22
+
23
+
24
+ def _set_str(setter: Callable, t, s: str) -> None:
25
+ """Call a CFFI string setter with a Python string argument."""
26
+ setter(t, ffi.new("char[]", s.encode()))
27
+
28
+
29
+ def _try_set(sym: str) -> Callable | None:
30
+ """Return a lib function if it exists, else None."""
31
+ try:
32
+ return getattr(lib, sym)
33
+ except AttributeError:
34
+ return None
35
+
36
+
37
+ def _maybe_set(setter_name: str, t, value: int) -> bool:
38
+ """Try to set a value using a setter if available; return True if set."""
39
+ fn = _try_set(setter_name)
40
+ if fn:
41
+ fn(t, int(value))
42
+ return True
43
+ return False
44
+
45
+
46
+ class Client:
47
+ """Client for running iperf3 tests using the provided configuration."""
48
+
49
+ def __init__(self, cfg: ClientConfig):
50
+ """Initialize the client with a configuration object."""
51
+ self.cfg = cfg
52
+
53
+ def run(self) -> Result:
54
+ """Run the iperf3 test synchronously and return the result."""
55
+ t = lib.iperf_new_test()
56
+ if t == ffi.NULL:
57
+ raise IperfLibraryError("iperf_new_test failed")
58
+ try:
59
+ _check(lib.iperf_defaults(t))
60
+ lib.iperf_set_test_role(t, b"c")
61
+ _set_str(lib.iperf_set_test_server_hostname, t, str(self.cfg.server))
62
+ lib.iperf_set_test_server_port(t, int(self.cfg.port))
63
+ lib.iperf_set_test_duration(t, int(self.cfg.duration))
64
+ if self.cfg.omit:
65
+ lib.iperf_set_test_omit(t, int(self.cfg.omit))
66
+ if self.cfg.parallel and self.cfg.parallel > 1:
67
+ lib.iperf_set_test_num_streams(t, int(self.cfg.parallel))
68
+ if self.cfg.blksize:
69
+ lib.iperf_set_test_blksize(t, int(self.cfg.blksize))
70
+ if self.cfg.tos is not None:
71
+ lib.iperf_set_test_tos(t, int(self.cfg.tos))
72
+
73
+ # reverse / bidirectional
74
+ if self.cfg.reverse:
75
+ lib.iperf_set_test_reverse(t, 1)
76
+ if self.cfg.bidirectional:
77
+ if not _maybe_set("iperf_set_test_bidirectional", t, 1):
78
+ raise UnsupportedFeatureError("Bidirectional not supported by this libiperf")
79
+
80
+ # mptcp
81
+ if self.cfg.mptcp:
82
+ if not _maybe_set("iperf_set_test_mptcp", t, 1):
83
+ raise UnsupportedFeatureError("MPTCP not supported by this libiperf")
84
+
85
+ # protocol-specific (UDP bitrate is optional setter name)
86
+ if self.cfg.protocol == Protocol.UDP:
87
+ # Numeric `rate` (bits per second) is the canonical lib API.
88
+ # Only support numeric rate in the lib backend; tests and
89
+ # callers should provide `rate: int`. If rate is absent,
90
+ # do not attempt to set bitrate on the lib path.
91
+ rate = getattr(self.cfg, "rate", None)
92
+ if rate is not None:
93
+ try:
94
+ rate_bps = int(rate)
95
+ except (TypeError, ValueError) as exc:
96
+ raise IperfError(f"invalid rate value: {rate}") from exc
97
+
98
+ # some libs expose iperf_set_test_bitrate, others expose
99
+ # a more generic iperf_set_test_rate; accept either.
100
+ fn = _try_set("iperf_set_test_bitrate") or _try_set("iperf_set_test_rate")
101
+ if fn is None:
102
+ raise UnsupportedFeatureError(
103
+ "This lib lacks 'iperf_set_test_bitrate' or 'iperf_set_test_rate'"
104
+ )
105
+ # pass integer value to the C API
106
+ fn(t, rate_bps)
107
+
108
+ # ensure JSON output
109
+ if not _maybe_set("iperf_set_test_json_output", t, 1):
110
+ # some extremely old libs may lack JSON setter; we rely on JSON for parsing
111
+ raise UnsupportedFeatureError("This libiperf lacks JSON output support")
112
+
113
+ _check(lib.iperf_run_client(t))
114
+ cjson = lib.iperf_get_test_json_output_string(t)
115
+ if cjson != ffi.NULL:
116
+ raw = json.loads(ffi.string(cjson).decode())
117
+ end = raw.get("end", {})
118
+
119
+ # minimal typed mapping into Result
120
+ def _sum(d) -> SumStats | None:
121
+ if not d:
122
+ return None
123
+ return SumStats.model_validate(
124
+ {
125
+ "bits_per_second": d.get("bits_per_second", 0.0),
126
+ "retransmits": d.get("retransmits"),
127
+ "lost_percent": d.get("lost_percent"),
128
+ "jitter_ms": d.get("jitter_ms"),
129
+ }
130
+ )
131
+
132
+ end_model = EndStats(
133
+ sum_sent=_sum(end.get("sum_sent")),
134
+ sum_received=_sum(end.get("sum_received")),
135
+ )
136
+ return Result(ok=True, raw=raw, end=end_model)
137
+ return Result(ok=False, error="No JSON returned by libiperf")
138
+ except IperfError as e:
139
+ return Result(ok=False, error=str(e))
140
+ finally:
141
+ lib.iperf_free_test(t)
142
+
143
+ async def arun(self) -> Result:
144
+ """Run the iperf3 test asynchronously and return the result."""
145
+ loop = asyncio.get_running_loop()
146
+ # use functools.partial to provide a zero-arg callable so static analyzers
147
+ # don't complain about unfilled *args parameter on run_in_executor
148
+ return await loop.run_in_executor(None, partial(self.run))
@@ -0,0 +1,100 @@
1
+ """Server wrapper and helpers for running iperf3 server tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import threading
7
+ from collections.abc import Callable
8
+ from functools import partial
9
+ from typing import Any
10
+
11
+ from .exceptions import IperfError, IperfLibraryError
12
+ from .ffi.api import ffi, lib
13
+
14
+
15
+ def _check(ret: int) -> None:
16
+ """Raise IperfError if the return code is negative."""
17
+ if ret < 0:
18
+ err = lib.iperf_strerror(lib.i_errno)
19
+ msg = ffi.string(err).decode() if err != ffi.NULL else "unknown libiperf error"
20
+ raise IperfError(msg)
21
+
22
+
23
+ def _set_str(setter: Callable[..., Any], t: Any, s: str) -> None:
24
+ """Call a CFFI string setter with a Python string argument."""
25
+ setter(t, ffi.new("char[]", s.encode()))
26
+
27
+
28
+ class Server:
29
+ """Minimal server wrapper. By default, it runs a single test, then returns.
30
+
31
+ For a persistent loop, call serve_forever().
32
+ """
33
+
34
+ def __init__(self, port: int = 5201, bind_host: str | None = None):
35
+ """Initialize the server with a port and optional bind host."""
36
+ self.port = port
37
+ self.bind_host = bind_host
38
+ # event used to cooperatively stop the serve_forever loop
39
+ self._stop_event = threading.Event()
40
+
41
+ def _run_iteration(self, t: Any) -> None:
42
+ """Run a single server iteration against an existing iperf_test `t`.
43
+
44
+ Extracted from the loop in `serve_forever` to make testing easier.
45
+ """
46
+ _check(lib.iperf_run_server(t))
47
+ lib.iperf_reset_test(t)
48
+
49
+ def run_once(self) -> None:
50
+ """Run the server for a single test and return."""
51
+ t = lib.iperf_new_test()
52
+ if t == ffi.NULL:
53
+ raise IperfLibraryError("iperf_new_test failed")
54
+ try:
55
+ _check(lib.iperf_defaults(t))
56
+ lib.iperf_set_test_role(t, b"s")
57
+ lib.iperf_set_test_server_port(t, int(self.port))
58
+ if self.bind_host:
59
+ _set_str(lib.iperf_set_test_server_hostname, t, self.bind_host)
60
+ # single iteration
61
+ self._run_iteration(t)
62
+ finally:
63
+ lib.iperf_free_test(t)
64
+
65
+ def serve_forever(self) -> None:
66
+ """Run the server loop until `stop()` is called.
67
+
68
+ This method creates a single iperf_test and reuses it across iterations.
69
+ The loop checks a threading.Event to allow cooperative shutdown.
70
+ """
71
+ t = lib.iperf_new_test()
72
+ if t == ffi.NULL:
73
+ raise IperfLibraryError("iperf_new_test failed")
74
+ try:
75
+ _check(lib.iperf_defaults(t))
76
+ lib.iperf_set_test_role(t, b"s")
77
+ lib.iperf_set_test_server_port(t, int(self.port))
78
+ if self.bind_host:
79
+ _set_str(lib.iperf_set_test_server_hostname, t, self.bind_host)
80
+ while not self._stop_event.is_set():
81
+ try:
82
+ self._run_iteration(t)
83
+ except IperfError:
84
+ # If stop() has been requested, suppress errors caused by
85
+ # the socket being closed during shutdown. Otherwise
86
+ # re-raise to surface unexpected server errors.
87
+ if self._stop_event.is_set():
88
+ break
89
+ raise
90
+ finally:
91
+ lib.iperf_free_test(t)
92
+
93
+ async def aserve_once(self) -> None:
94
+ """Run the server for a single test asynchronously."""
95
+ loop = asyncio.get_running_loop()
96
+ await loop.run_in_executor(None, partial(self.run_once))
97
+
98
+ def stop(self) -> None:
99
+ """Signal the serve_forever loop to stop."""
100
+ self._stop_event.set()
iperf3_lib/result.py ADDED
@@ -0,0 +1,42 @@
1
+ """Result and statistics models for iperf3 test runs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class SumStats(BaseModel):
11
+ """Summary statistics for a test direction (sent/received)."""
12
+
13
+ bits_per_second: float = Field(0, alias="bits_per_second")
14
+ retransmits: int | None = None
15
+ lost_percent: float | None = None
16
+ jitter_ms: float | None = None
17
+
18
+
19
+ class EndStats(BaseModel):
20
+ """End-of-test statistics for both directions."""
21
+
22
+ sum_sent: SumStats | None = None
23
+ sum_received: SumStats | None = None
24
+
25
+
26
+ class Result(BaseModel):
27
+ """Top-level result object for an iperf3 test run."""
28
+
29
+ ok: bool
30
+ error: str | None = None
31
+ raw: dict[str, Any] = {}
32
+ end: EndStats | None = None
33
+
34
+ @property
35
+ def summary_mbps(self) -> float:
36
+ """Return the summary throughput in Mbps, or 0.0 if unavailable."""
37
+ if not self.end:
38
+ return 0.0
39
+ for part in (self.end.sum_sent, self.end.sum_received):
40
+ if part and part.bits_per_second:
41
+ return part.bits_per_second / 1_000_000.0
42
+ return 0.0
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: iperf3-lib
3
+ Version: 0.1.0
4
+ Summary: Modern Python wrapper for iperf3 using cffi (libiperf), with async support.
5
+ Project-URL: Homepage, https://github.com/dariuszpanas/iperf3-lib
6
+ Project-URL: Documentation, https://github.com/dariuszpanas/iperf3-lib/tree/main/docs
7
+ Project-URL: Repository, https://github.com/dariuszpanas/iperf3-lib
8
+ Project-URL: Issues, https://github.com/dariuszpanas/iperf3-lib/issues
9
+ Author: Dariusz Panas
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: MPTCP,SCTP,async,benchmark,cffi,iperf3,network,throughput
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: System Administrators
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: System :: Benchmark
24
+ Classifier: Topic :: System :: Networking
25
+ Requires-Python: >=3.12
26
+ Requires-Dist: cffi>=1.16
27
+ Requires-Dist: pydantic>=2.8
28
+ Description-Content-Type: text/markdown
29
+
30
+ # iperf3-lib
31
+
32
+ Modern Python wrapper for **iperf3** using **cffi** against **libiperf**.
33
+ - Typed configs/results (Pydantic)
34
+ - Sync + Async APIs
35
+ - Feature detection for SCTP / bidirectional / MPTCP (when lib supports it)
36
+ - Ready for `uv` workflows, ruff/ty/pytest
37
+
38
+ ## Quickstart
39
+
40
+ ```bash
41
+ # create env & install
42
+ uv venv
43
+ source .venv/bin/activate
44
+ uv pip install -e ".[dev]"
45
+ ```
46
+
47
+ You need a system libiperf (install iperf3 via your package manager).
48
+ To point to a non-standard library path:
49
+
50
+ ```bash
51
+ export IPERF3_LIB=/usr/local/lib/libiperf.so
52
+ ```
53
+
54
+ ```python
55
+ from iperf3_lib.config import ClientConfig, Protocol
56
+ from iperf3_lib.iperf_client import Client
57
+
58
+ cfg = ClientConfig(server="127.0.0.1", duration=2, protocol=Protocol.TCP, parallel=2)
59
+ res = Client(cfg).run()
60
+ print("OK:", res.ok, "Mbps:", f"{res.summary_mbps:.2f}", "err:", res.error)
61
+ ```
62
+ Notes
63
+ * Full features require a recent libiperf (iperf3 ≥ 3.19.x recommended).
64
+ * Some symbols may not exist on older builds; we detect and degrade gracefully.
65
+ * Integration tests are marked with @pytest.mark.integration.
66
+
67
+ ---
68
+
69
+ ## Running tests
70
+
71
+ - Run tests locally with Docker (recommended for Windows):
72
+
73
+ ```bash
74
+ # build the test image (pick python base and iperf version if needed)
75
+ docker build --build-arg PYTHON_BASE=python:3.12-slim --build-arg IPERF3_VERSION=3.20 -t py-iperf3-test .
76
+
77
+ # run the full test suite inside the container (mounted volume allows editing without rebuilding)
78
+ docker run --rm -v "$(pwd):/app" py-iperf3-test bash -c "pytest -vv --cov=src --cov-report=term-missing"
79
+ ```
80
+
81
+ - Quick single-test run (example):
82
+
83
+ ```bash
84
+ docker run --rm -v "$(pwd):/app" py-iperf3-test bash -c "pytest -q tests/test_client_integration_configs.py::test_client_various_configs\[udp_rate\] -vv"
85
+ ```
86
+
87
+ Notes about optional features
88
+ - MPTCP and some other features are optional and require the libiperf to be built with kernel/header support. Tests that exercise optional features will be skipped or xfailed depending on whether the built shared object exports the required symbol.
89
+
90
+ CI
91
+ - This repository includes a GitHub Actions workflow that builds and runs the tests inside the Docker image for a matrix of Python base images and iperf versions (see `.github/workflows/ci.yml`).
92
+
93
+ ---
94
+
95
+ ```markdown
96
+ # Changelog
97
+
98
+ ## 0.1.0
99
+ - Initial release: cffi (ABI) wrapper of libiperf (client & server),
100
+ Pydantic models, async support, ruff/ty/pytest, CI with uv.
101
+
102
+ ```
@@ -0,0 +1,14 @@
1
+ iperf3_lib/__init__.py,sha256=2Bldlyrdgv8BTFE-CDtpaZTNcUpccuzalF-5yGj5IO0,292
2
+ iperf3_lib/capabilities.py,sha256=U-RvwOJZ7fxbJbKwbpSgI2uqazR2xMwLW0p-rOzuCZs,691
3
+ iperf3_lib/config.py,sha256=DA0_5BKsCncXIrUTqR8zImFehO71zlBW-6aMKxDFX2A,1027
4
+ iperf3_lib/exceptions.py,sha256=4DL__8I-6JQAEFE3vQQ7nqLvuvZwIU-ld5w5Lp6tFzc,412
5
+ iperf3_lib/iperf_client.py,sha256=pp-pMTGnKMD6phfAJoRaM4nQO9imVspEkhwkNsKGUp8,6166
6
+ iperf3_lib/iperf_server.py,sha256=EOg2h0zYlWTKFZGDkTfVdAE9XkgCZ5OLnrA22ReDdRw,3623
7
+ iperf3_lib/result.py,sha256=oqx6dvJRMWuzLl8byFuWHZoa2tJl3WjI_fvuNdRZdig,1138
8
+ iperf3_lib/ffi/__init__.py,sha256=XoSMHMQjmAG3jWmwrX_nr9dWqJ_uwGGaSTNp_3onHpw,54
9
+ iperf3_lib/ffi/api.py,sha256=vgEP9zXjOT-k1X1LCd9tQjcyZZ7WSEdwj5vsIQ8imDw,882
10
+ iperf3_lib/ffi/symbols.py,sha256=Nj7EJHzYQsvpArPGyh_ATcNS4euYWaOdHIDS0JgMrJY,1382
11
+ iperf3_lib-0.1.0.dist-info/METADATA,sha256=sTtrXoVuXYZibHZuyN9gXHAOEumkEvpIs2sg3muA7qQ,3669
12
+ iperf3_lib-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
+ iperf3_lib-0.1.0.dist-info/licenses/LICENSE,sha256=QeJgmhetzHVJfZZkd6GkMm1jtRiX2P_GaeEKC_3b4bo,1070
14
+ iperf3_lib-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dariusz Panas
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.