Full HTTP and WebSocket traffic over any text-based transport — messengers, SMS, or anything else that can carry a string.
Project description
Aethernet (Æthernet)
Full HTTP and WebSocket traffic over any message-based transport — strings or bytes.
Aethernet is a lightweight tunneling library that lets you keep using familiar httpx and WebSocket-style APIs even when you cannot open direct TCP connections. All traffic is framed, optionally encrypted, batched, and delivered through a LowTransport that you implement (Telegram, SMS, serial, BLE, sockets, etc.).
Why Aethernet?
Some environments can send/receive messages but cannot reach the internet directly: Telegram bots, SMS gateways, air‑gapped systems, embedded radios, BLE links, etc. Aethernet bridges that gap: your code keeps using standard HTTP/WebSocket clients while the real bytes travel over whatever channel you have.
Features
- Drop-in HTTP client transport — compatible with
httpx.AsyncClient(transport=...) - WebSocket support — via
AethernetWebSockets - String or bytes low-level transport
mode="bytes"(default) for binary-safe channelsmode="string"for messengers/SMS (alphabet + size constraints)
- End-to-end encryption (optional) with selectable AEAD:
CHACHA20_POLY1305AES_GCMAES_EAX
- Reliability (Retry / ARQ) layer (optional)
NONE— best-effortSTOP_AND_WAIT— classic ARQPARALLEL— selective-repeat style with a window
- Aggregation & flow control — batching and chunking over small-message channels
- Bring your own transport — implement one abstract class (
LowTransport) and configure it viaLowTransportConfig
Installation
# pip
pip install aethernet-core
# uv
uv add aethernet-core
Public API (what you import)
Everything below is available from the package root:
from aethernet import (
EncryptionMode,
ReliabilityMode,
LowTransport,
LowTransportConfig,
get_transport,
AethernetHttpx,
AethernetWebSockets,
AethernetServer,
)
Core concept: LowTransport
A LowTransport is the only thing you implement. It must be able to:
send(data: str | bytes) -> Nonerecv() -> str | bytes
Configuration via CONFIG (important)
Transport limits and timings live in a dataclass LowTransportConfig and are typically provided via a class attribute CONFIG.
Key points:
- You can keep the default
CONFIG, but it’s recommended to set it explicitly for your transport. - Every implementor must:
- accept
config: LowTransportConfig | None = Nonein__init__ - call
super().__init__(config)inside__init__
- accept
This enables users to safely tweak parameters with dataclasses.replace(...) and pass the modified config to your transport.
Quick Start
Aethernet has two sides:
- Client — makes HTTP/WebSocket requests
- Server — runs on a machine with internet access and executes requests on behalf of the client
Both sides must use:
- the same
LowTransportimplementation (or compatible ones) - the same
encryption_mode - the same
encryption_key(if encryption is enabled) - the same
reliability_mode
1) Define your transport (bytes-mode example)
from aethernet import LowTransport, LowTransportConfig
class MyTransport(LowTransport):
# Recommended: define constraints/timings here
CONFIG = LowTransportConfig(
mode="bytes",
max_message_bytes=1024,
min_send_interval=0.2,
min_recv_interval=0.2,
delay_before_resending=8.0, # used by reliability layer
)
def __init__(self, *, config: LowTransportConfig | None = None) -> None:
super().__init__(config)
# initialize your channel here (serial/socket/BLE/etc.)
def close(self) -> None:
# release resources (optional)
pass
def send(self, data: bytes) -> None:
# deliver `data` through your channel
raise NotImplementedError
def recv(self) -> bytes:
# return the next incoming message
raise NotImplementedError
2) Client side
import asyncio
import secrets
import httpx
from aethernet import (
get_transport,
AethernetWebSockets,
EncryptionMode,
ReliabilityMode,
)
SHARED_KEY = secrets.token_bytes(32) # generate once, share securely
async def main() -> None:
link = await get_transport(
MyTransport(),
encryption_mode=EncryptionMode.CHACHA20_POLY1305,
encryption_key=SHARED_KEY,
reliability_mode=ReliabilityMode.PARALLEL,
window_size=8,
)
# --- HTTP ---
async with httpx.AsyncClient(transport=link) as client:
r = await client.get("https://httpbin.org/get")
print(r.status_code, r.json())
# --- WebSocket ---
async with AethernetWebSockets(link).connect("wss://echo.websocket.events") as ws:
await ws.send("hello")
msg = await ws.recv()
print("Echo:", msg)
if __name__ == "__main__":
asyncio.run(main())
3) Server side
Run this where internet is available and where your LowTransport can exchange messages with the client.
import asyncio
from aethernet import AethernetServer, EncryptionMode, ReliabilityMode
SHARED_KEY = b"..." # must match client's key exactly
async def main() -> None:
server = await AethernetServer.create(
MyTransport(),
encryption_mode=EncryptionMode.CHACHA20_POLY1305,
encryption_key=SHARED_KEY,
reliability_mode=ReliabilityMode.PARALLEL,
window_size=8,
)
print("Aethernet server is running...")
await server.start_and_wait()
if __name__ == "__main__":
asyncio.run(main())
String-mode transports (Telegram/SMS/etc.)
If your channel only supports text, use mode="string" and specify an alphabet (allowed characters) and limits.
from aethernet import LowTransport, LowTransportConfig
B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
class SmsLikeTransport(LowTransport):
CONFIG = LowTransportConfig(
mode="string",
max_message_chars=1000,
alphabet=B64_ALPHABET,
min_send_interval=1.0,
min_recv_interval=1.0,
)
def __init__(self, *, config: LowTransportConfig | None = None) -> None:
super().__init__(config)
def send(self, data: str) -> None:
# In string mode you will typically send `str` messages
# (the stack will produce appropriate payloads for your mode).
raise NotImplementedError
def recv(self) -> str:
raise NotImplementedError
Tweaking transport limits with dataclasses.replace
Because config lives in a dataclass, users can override just a few fields:
from dataclasses import replace
cfg = replace(MyTransport.CONFIG, min_send_interval=0.6, max_message_bytes=2048)
low = MyTransport(config=cfg)
Transport stack creation: get_transport(...)
async def get_transport(
low_transport: LowTransport,
*,
# Encryption
encryption_mode: EncryptionMode = EncryptionMode.NONE,
encryption_key: bytes | None = None,
# Aggregating
flush_interval: float = 0.5,
max_batch_size: int = 64 * 1024,
chunk_assembly_ttl: float = 60.0,
# Reliability
reliability_mode: ReliabilityMode = ReliabilityMode.NONE,
window_size: int = 8,
ack_flush_interval: float = 0.1,
ack_batch_size: int = 8,
received_seqs_window: int = 256,
reorder_buffer_ttl: float = 500.0,
# Logging
logger=None,
) -> "AggregatingLink"
Parameters overview
Encryption
encryption_modeEncryptionMode.NONEEncryptionMode.CHACHA20_POLY1305EncryptionMode.AES_GCMEncryptionMode.AES_EAX
encryption_key: shared secret key (must match on both ends). Required ifencryption_mode != NONE.
Aggregating (batching/chunking)
flush_interval: max time to wait before flushing a batchmax_batch_size: max logical batch size in bytes (before chunking)chunk_assembly_ttl: time to keep incomplete chunk assemblies
Reliability (Retry / ARQ)
reliability_modeReliabilityMode.NONEReliabilityMode.STOP_AND_WAITReliabilityMode.PARALLEL(useswindow_size)
window_size: send window forPARALLELack_flush_interval,ack_batch_size: ACK pacing / batchingreceived_seqs_window: deduplication memoryreorder_buffer_ttl: how long to keep out-of-order packets while reordering
Logging
logger: optional logger for debugging/observability
Server creation: AethernetServer.create(...)
AethernetServer.create(...) mirrors get_transport(...) and additionally configures the HTTP router and SSE flushing:
@classmethod
async def create(
cls,
low_transport: LowTransport,
*,
# Encryption
encryption_mode: EncryptionMode = EncryptionMode.NONE,
encryption_key: bytes | None = None,
# Aggregating
flush_interval: float = 0.5,
max_batch_size: int = 64 * 1024,
chunk_assembly_ttl: float = 60.0,
# Reliability
reliability_mode: ReliabilityMode = ReliabilityMode.NONE,
window_size: int = 8,
ack_flush_interval: float = 0.1,
ack_batch_size: int = 8,
received_seqs_window: int = 256,
reorder_buffer_ttl: float = 500.0,
# Server Router
http_client=...,
proxy_http_client=...,
# HTTP Aggregating
sse_flush_bytes: int = 65536,
sse_flush_interval: float = 0.5,
# Logging
logger=...,
) -> "AethernetServer"
Extra server parameters
http_client: thehttpx.AsyncClientused by the server to perform real outgoing requestsproxy_http_client: optional separate client (e.g. for proxying)sse_flush_bytes,sse_flush_interval: flushing behavior for SSE streaming responses
Enums
Reliability
from aethernet import ReliabilityMode
ReliabilityMode.NONE
ReliabilityMode.STOP_AND_WAIT
ReliabilityMode.PARALLEL
Encryption
from aethernet import EncryptionMode
EncryptionMode.NONE
EncryptionMode.CHACHA20_POLY1305
EncryptionMode.AES_GCM
EncryptionMode.AES_EAX
Project Structure
aethernet-core/
├── .github/
├── src/
│ └── aethernet/
│ ├── transport/
│ ├── __init__.py
│ ├── exceptions.py
│ ├── server_router.py
│ └── typing.py
├── tests/
├── pyproject.toml
├── .gitignore
├── LICENSE
├── NOTICE
├── uv.lock
├── README.ru.md
└── README.md
Development
uv sync --extra dev
pytest
black --check .
ruff check .
mypy .
Compatibility
| Python | Platforms |
|---|---|
| 3.12 + | Linux, macOS, Windows |
Bug Reports
Please open an issue and include:
- Expected behavior
- Actual behavior
- Minimal reproduction
License
Author
Walter Kerrigan (esolment) — https://github.com/esolment
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aethernet_core-2.0.1.tar.gz.
File metadata
- Download URL: aethernet_core-2.0.1.tar.gz
- Upload date:
- Size: 53.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
18fc70dd397c4e35af4c56b89b0cbb6830545e366cc4f685c2e8ca85aed1b70f
|
|
| MD5 |
f05e71da0e3c3f55cd96bc9279da478f
|
|
| BLAKE2b-256 |
09ecfaf37130b50c1e003ea7bea74d03276eede4d55d2414505e5d03c02ab8bc
|
Provenance
The following attestation bundles were made for aethernet_core-2.0.1.tar.gz:
Publisher:
release.yml on solment-slet/aethernet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aethernet_core-2.0.1.tar.gz -
Subject digest:
18fc70dd397c4e35af4c56b89b0cbb6830545e366cc4f685c2e8ca85aed1b70f - Sigstore transparency entry: 1358808014
- Sigstore integration time:
-
Permalink:
solment-slet/aethernet@ae26e3732fff150416315406e6b9c364be322a54 -
Branch / Tag:
refs/tags/v2.0.1 - Owner: https://github.com/solment-slet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ae26e3732fff150416315406e6b9c364be322a54 -
Trigger Event:
release
-
Statement type:
File details
Details for the file aethernet_core-2.0.1-py3-none-any.whl.
File metadata
- Download URL: aethernet_core-2.0.1-py3-none-any.whl
- Upload date:
- Size: 42.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aeccfbcce18a73806bd6716cba2091c6ffefdabaf0da705c4ea639d8e5505563
|
|
| MD5 |
008b23a828086026785f8a23eef49520
|
|
| BLAKE2b-256 |
dab15316c5496934fcfcbae63dc6668b287f5f120f458291345e993bc8b3cff2
|
Provenance
The following attestation bundles were made for aethernet_core-2.0.1-py3-none-any.whl:
Publisher:
release.yml on solment-slet/aethernet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aethernet_core-2.0.1-py3-none-any.whl -
Subject digest:
aeccfbcce18a73806bd6716cba2091c6ffefdabaf0da705c4ea639d8e5505563 - Sigstore transparency entry: 1358808200
- Sigstore integration time:
-
Permalink:
solment-slet/aethernet@ae26e3732fff150416315406e6b9c364be322a54 -
Branch / Tag:
refs/tags/v2.0.1 - Owner: https://github.com/solment-slet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ae26e3732fff150416315406e6b9c364be322a54 -
Trigger Event:
release
-
Statement type: