NLTP (pynltp v1.3.2)
Loss-resilient, ChaCha20-Poly1305-encrypted binary, file, telemetry, and priority-command transport with GF(2^8) Cauchy forward error correction — running identically over UDP/IP (LAN, Wi-Fi, 4G/5G CGNAT, satellite) and routerless 2.4 GHz ESP-NOW across Python, JavaScript/TypeScript, Java, Android, and ESP32/ESP8266.
1. Overview · 2. Architecture · 3. ESP UDP & ESP-NOW · 4. Platform Guides & Examples · 5. NLTP vs. MQTT / TCP / ESP-NOW · 6. Benchmarks · 7. Repo Layout & Tests
1. What Is NLTP?
NLTP (NeckLink Transport Protocol) is a lightweight, reliable, encrypted datagram transport built for links where standard protocols break down: congested 2.4 GHz RF, weak outdoor Wi-Fi, 4G/5G cellular connections behind carrier CGNAT, long-RTT radio/satellite links, and routerless microcontroller meshes.
- TCP and QUIC collapse on bursty wireless packet loss (
5–35%fades) while waiting on round-trip retransmission timers. - MQTT requires a TCP broker, stalls behind head-of-line blocking during drops, and cannot run directly between microcontrollers without a Wi-Fi router.
- Raw ESP-NOW fires fast 2.4 GHz frames without a router, but is capped at
250 B(v1) or1,470 B(v2) per frame, drops multi-kilobyte payloads when a single packet is lost, limits hardware encryption to 6–17 peers, and cannot cross IP routers or cellular networks.
NLTP unifies embedded boards, mobile apps, browsers, laptops, and cloud servers under one unmodified "NL" binary wire format:
- Forward Error Correction (
GF(2^8)Cauchy Reed-Solomon): Reconstructs lost packets algebraically inside 8-shard generations without waiting for per-packet retransmission timeouts — delivering100%SHA-256-verified payloads across0–35%packet loss. - 256-Bit Authenticated Encryption (
ChaCha20-Poly1305): Built-inRFC 8439AEAD with 20-byte header authentication,RFC 647964-packet anti-replay window, and end-to-endSHA-256stream receipts (216/216cross-language tests passed). - Priority Out-of-Band Command Lane (
send_reflex): Urgent control frames (STOP,RESET,PING_ACTUATOR) bypass bulk file transfers with zero head-of-line blocking and retransmit until explicitly acknowledged (PT_REFLEX_ACK). - One Protocol Across 5 Runtimes & 2 Physical Transports: Identical wire bytes run over UDP/IP (including built-in
RFC 5389STUN hole-punching and zero-knowledge 1-port CGNAT relay) and directly inside IEEE 802.11 ESP-NOW frames onESP32andESP8266.
2. How NLTP Works — Structured Architecture
flowchart LR
subgraph Sender["Sender (Python / JS / Java / Android / ESP)"]
S1["Payload Bytes or File + SHA-256"] --> S2["Split into k=8 Shards (960 B or 192 B)"]
S2 --> S3["GF(2^8) Cauchy Parity Generator"]
S3 --> S4["1-RTT Adaptive Token-Bucket Pacer"]
R1["Urgent Command (send_reflex)"] -.->|"Bypasses Bulk Queue"| S5["ChaCha20-Poly1305 AEAD + 'NL' Frame"]
S4 --> S5
end
S5 ===>|"UDP/IP, CGNAT Relay, WebRTC, or ESP-NOW"| RX1
subgraph Receiver["Receiver (Any NLTP Runtime)"]
RX1["Verify Poly1305 Tag + RFC 6479 Replay Window"] --> RX2["Incremental GF(2^8) Gauss-Jordan Decoder"]
RX2 --> RX3["Verify Full-Stream SHA-256 -> Emit STREAM_FIN_ACK"]
RX1 -.->|"Deduplicate + Emit PT_REFLEX_ACK"| RX4["Deliver Priority Reflex Immediately"]
end
2.1 Wire Frame & Cryptographic Envelope
Every NLTP packet — whether carried inside a UDP datagram, a CGNAT relay envelope, a WebRTC data channel, or a routerless ESP-NOW action frame — starts with magic bytes "NL" (0x4E 0x4C):
| Mode | Header Size | Wire Layout | Integrity & Replay Protection |
|---|---|---|---|
Encrypted (FLAG_ENCRYPTED) |
20 B + 16 B tag |
"NL" (2 B) | ver_type (1 B) | flags (1 B) | conn_id (4 B) | ts_ms (4 B) | seq64 (8 B) | ciphertext | poly1305_tag (16 B) |
256-bit ChaCha20-Poly1305 (RFC 8439) with header AAD + RFC 6479 64-packet sliding anti-replay bitmap |
| Plaintext | 16 B |
"NL" (2 B) | ver_type (1 B) | flags (1 B) | conn_id (4 B) | ts_ms (4 B) | crc32 (4 B) | payload |
IEEE CRC-32 over header + payload (rejects RF corruption; encryption endpoints reject plaintext downgrades) |
Key derivation is identical across Python, C99, JS, Java, and Arduino: a 32-byte raw/Base64 key is used directly (nltp gen-key), while any human passphrase is hashed with SHA-256 into a 256-bit key.
2.2 GF(2^8) Cauchy FEC & Automatic Shard Sizing
- Stream Announcement (
PT_STREAM_META): Carriesstream_id, total byte length, filename, shard size, and expected full-payloadSHA-256digest. - Generation Slicing (
k = 8shards per generation): Payloads are sliced into sliding generations of up to 8 systematic data shards (PT_DATA_SHARD). - Proactive + Reactive Cauchy Parity (
PT_CRYPTIC_PARITY): OverGF(2^8)(irreducible polynomial0x11D), each parity shard is a linear combination of thekdata shards using a Cauchy matrix row. Anykdistinct shards (data or parity, in any order) solve the generation via incremental Gauss-Jordan elimination on the receiver. - Automatic Dual Shard Width:
- Standard Mode (
960 Bdata shard →1,010 Bencrypted frame): Fits cleanly inside Internet UDP (1,500 BEthernet /1,280 BIPv6 /4Gtunnels) and ESP-NOW v2 (1,470 BMTU). - Compact Mode (
192 Bdata shard →242 Bencrypted frame): Fits inside ESP-NOW v1 (250 BMTU) onESP8266and legacy radios. - Receivers read
row_widthdirectly from each shard header (12 B), so960 Band192 Bsenders interoperate automatically with zero receiver configuration.
- Standard Mode (
- 1-RTT Adaptive Rate Pacing &
SHA-256Completion: Selective acknowledgements (PT_PROPRIOCEPTIVE_ACK) report missing degrees of freedom (DOF) per generation plus measured arrival rate, letting the sender pace below bottleneck queue limits. Once all generations solve, the receiver checks the full-streamSHA-256and returnsPT_STREAM_FIN_ACK.
2.3 Priority Reflex Lane (PT_REFLEX_MSG / PT_REFLEX_ACK)
Calling send_reflex() / sendReflex() emits a standalone encrypted control packet outside the bulk generation queue:
- Zero head-of-line blocking: Delivered immediately even while a multi-megabyte file transfer is repairing lost shards.
- Confirmed exactly-once delivery: Retransmitted with exponential backoff (
15–60 msinitial, up to250 ms,3 sbudget) untilPT_REFLEX_ACKarrives; receivers deduplicate by(conn_id, seq).
2.4 NAT, Firewall & 4G/5G CGNAT Traversal
- Live Socket STUN (
RFC 5389):discover_wan()queries public STUN servers from the active NLTP socket to detect Endpoint-Independent vs. Symmetric CGNAT without port races. - Low-TTL UDP Hole-Punching (
punch_p2p): Opens local NAT pinholes (TTL=2) before full-TTL authenticatedPING/PONGprobing. - Connection Migration (
0-RTTRoaming): Sessions track peers by authenticatedconn_id, allowing a client or phone switching between Wi-Fi and cellular (rebind_socket()) to continue mid-transfer without dropping state. - Zero-Knowledge 1-Port Relay (
NLTPRelayHub): When both peers sit behind symmetric carrier CGNAT, a lightweight UDP relay routes packets by 16-byte hashed room tag (BLAKE2s/SHA-256) while holding zero encryption keys.
3. How NLTP Works on ESP: UDP Mode vs. ESP-NOW Mode vs. Hybrid Bridge
On Espressif microcontrollers (ESP32, ESP32-S2/S3, ESP32-C3/C5/C6, and ESP8266), the exact same zero-heap ANSI C99 NLTP state machine (nltp_endpoint_t) runs over two interchangeable physical transports that can also be bridged together:
flowchart TB
subgraph Mesh["Off-Grid 2.4 GHz ESP-NOW Mesh (No Wi-Fi Router, IP = 0.0.0.0)"]
E8266["ESP8266 Sensor (ESP-NOW v1: 192 B shards / 242 B L2)"]
E32A["ESP32 Node (ESP-NOW v2: 960 B shards / 1,010 B L2)"]
E8266 <-->|"Unmodified 'NL' Frames (Ch 1-14)"| E32A
end
E8266 -->|"242 B ESP-NOW v1"| GW
E32A <-->|"1,010 B ESP-NOW v2"| GW
subgraph Bridge["ESP32 Gateway or Standalone Wi-Fi / 4G ESP32"]
GW["ESP32 Node: NLTPESPNowNode + NLTPWiFiNode"]
end
GW <-->|"Direct Wi-Fi / LAN UDP"| SRV["Python · Node.js · Java · Android"]
GW <-->|"4G/5G SIM -> 1-Port NLTPRelayHub"| SRV
Structured Comparison of ESP Operating Modes
| Aspect | Mode A: NLTP over UDP (NLTPWiFiNode) |
Mode B1: NLTP over ESP-NOW v2 (NLTPESPNowNode) |
Mode B2: NLTP over ESP-NOW v1 (ESP8266 / v1 Flag) |
Mode C: ESP-NOW ↔ UDP Gateway |
|---|---|---|---|---|
| Supported Chips | ESP32 family, RP2040-W, any <WiFiUdp.h> board |
ESP32, S2, S3, C3, C5, C6 (ESP-IDF 5.x / Arduino core 3.x) |
ESP8266 (NodeMCU) + any ESP32 with setEspNowV1Mode(true) |
ESP32 (runs ESP-NOW + Wi-Fi STA on shared radio channel) |
| Router / IP Needed? | Yes — Wi-Fi AP, Ethernet, or 4G/5G hotspot | No — direct 2.4 GHz L2 action frames (wifi_sta=0) |
No — direct 2.4 GHz L2 action frames (wifi_sta=0) |
Leaf nodes: No; Gateway: Yes (Wi-Fi or 4G SIM) |
| Internet / Cloud Reach | Direct (LAN, WAN, STUN, or beginRelay()) |
Local 2.4 GHz hop (~100–800 m, or LR mode) |
Local 2.4 GHz hop (~100–400 m) |
Full end-to-end from off-grid leaf nodes to cloud/mobile |
| Shard / Wire Size | 960 B shard → 1,010 B UDP payload |
960 B shard → 1,010 B L2 frame (<= 1,470 B MTU) |
192 B shard → 242 B L2 frame (<= 250 B MTU) |
Accepts both 192 B & 960 B shards automatically |
| Measured Goodput | Up to 3.04 Mbps (PC ↔ Router ↔ ESP32) |
17.75 Mbps median / 18.35 Mbps max (32 KB AEAD) |
Up to 7.25 Mbps (NodeMCU → ESP32 8 KB AEAD) |
Limited by upstream Wi-Fi / 4G link |
| Loss Survival | Verified to 20.8% loss (100% SHA-256) |
Verified 0–35% loss (100% SHA-256) |
Verified 0–35% cross-chip loss (100% SHA-256) |
Independent FEC recovery on each hop (or L2 cut-through) |
Key Engineering Details on ESP
- Zero L2 Wrapper Overhead:
nltp_espnow.hplaces the raw"NL"datagram directly inside the IEEE 802.11 vendor action payload with zero extra headers. A gateway can either verify-and-forward complete files (NLTPESPNowNode→NLTPWiFiNode) or forward raw"NL"packets byte-for-byte into a UDP socket orNLTPRelayHub. - Cross-Chip
ESP8266 ↔ ESP32Compatibility: AnESP8266(80 KBtotal SRAM) compiled with-DNLTP_SHARD_BYTES=192 -DNLTP_RX_SLOTS=2transmits242 Bencrypted shards that anyESP32v2 hub (1,470 BMTU) decodes natively alongside1,010 Bv2 shards from otherESP32peers. - Hardened Multi-Peer Hub (
nltp_espnow_hub_t):- Pre-Auth Slot Gate: Unknown sender MACs must pass
ChaCha20-Poly1305(orallow_cb+CRC-32) verification before any RAM slot or ESP-NOW peer entry is allocated — spoofed MAC floods allocate0slots. - Automatic 20-Peer Hardware Table Management: ESP-NOW hardware supports at most 20 total peers (
6–17encrypted). NLTP encrypts at the payload layer with 256-bitChaCha20-Poly1305and LRU-evicts idle peer entries automatically, so a single hub can talk to an unlimited population of intermittent field sensors. - Mid-Stream Takeover Guard (
v1.3.2): An active receive stream cannot be reset by a delayed replay or strayconn_idpacket unless the stream finishes or goes silent for>= 1,000 ms, keeping deep-sleep wake re-connections fast (4.34 msmedian) without mid-transfer stalls.
- Pre-Auth Slot Gate: Unknown sender MACs must pass
4. Platform Guides & Basic Connection Examples
4.1 Python (pynltp / import nltp + nltp CLI)
How it works: Runs on Python 3.9+ (Windows, Linux, macOS, Raspberry Pi) using only numpy and cryptography (with a pure-Python ChaCha20-Poly1305 fallback). NLTPServer and NLTPClient manage a background non-blocking UDP selector thread for 1-RTT packet pacing, FEC encoding/decoding, keepalives, and reflex acknowledgements.
# Install v1.3.2 directly from PyPI:
pip install --upgrade pynltp
# Or install directly from GitHub source:
pip install "git+https://github.com/indian-aman/necklink.git#subdirectory=packages/nltp"
Basic Python Connection (Server + Client on LAN or Internet):
from nltp import NLTPServer, NLTPClient, generate_nltp_key
key = generate_nltp_key() # 256-bit ChaCha20-Poly1305 shared key
# 1. Start a server listening on UDP port 9000
with NLTPServer(("0.0.0.0", 9000), save_dir="./inbox", psk=key) as server:
# 2. Connect a client to the server
with NLTPClient(("127.0.0.1", 9000), psk=key, max_tx_kbps=20_000) as client:
# Send a priority reflex command (confirmed out-of-band)
seq = client.send_reflex("STOP")
assert client.wait_reflex_ack(seq, timeout=3.0)
# Send raw bytes, JSON, or a file (Cauchy FEC + SHA-256 verified)
receipt = client.send_bytes(b"hello over loss-resilient UDP")
assert receipt["verified_sha256"] is True
# Receive items on the server
reflex_item = server.recv(timeout=2.0) # kind == "reflex", data == b"STOP"
data_item = server.recv(timeout=2.0) # kind == "bytes", sha256_verified == True
# Server can also reply two-way to the client over the same UDP port
server.send_bytes(data_item.peer_addr, b"ack: batch stored")
reply = client.recv(timeout=2.0)
print("Client received:", reply.data.decode())
Command-Line Usage & CGNAT Relay:
# Generate a 256-bit key and inspect your NAT type via STUN
nltp gen-key
nltp stun
# Direct UDP transfer
nltp serve --host 0.0.0.0 --port 9000 --psk "<KEY>" --save-dir ./inbox
nltp send-file --host <SERVER_IP> --port 9000 --psk "<KEY>" --file firmware.bin
# Behind 4G/5G Carrier CGNAT (run relay on any 1-port cloud VM — holds zero keys)
nltp relay --host 0.0.0.0 --port 9000
nltp serve --relay <RELAY_IP>:9000 --room device-01 --psk "<KEY>" --save-dir ./inbox
nltp send-file --relay <RELAY_IP>:9000 --room device-01 --psk "<KEY>" --file sensor_batch.bin
4.2 JavaScript & TypeScript (Node.js UDP + Browser / WebRTC)
How it works: Zero external npm dependencies (Node.js >= 18, Deno, Bun, and modern Browsers). Ships dual ESM + CommonJS entrypoints and full .d.ts TypeScript declarations:
import { NLTPServer, NLTPClient, NLTPRelayHub } from 'nltp'usesnode:dgramfor native UDP networking.import { NLTPEndpoint } from 'nltp/core'is 100% transport-agnostic (zero Node imports) so you can run NLTP over browser WebRTCRTCDataChannel, WebTransport datagrams, WebSockets, or WebSerial.
npm install ./packages/nltp/js
Basic Node.js UDP Connection:
import { NLTPServer, NLTPClient, generateNltpKey } from 'nltp';
const key = generateNltpKey();
const server = await NLTPServer.listen({ host: '0.0.0.0', port: 9000, psk: key, saveDir: './inbox' });
const client = await NLTPClient.connect({ host: '127.0.0.1', port: 9000, psk: key, maxTxKbps: 20_000 });
// Confirmed urgent command + verified binary/JSON transfer
const seq = await client.sendReflex('PING_DEVICE');
await client.waitReflexAck(seq, 3000);
const receipt = await client.sendJson({ deviceId: 'node-01', status: 'ok' });
console.log('SHA-256 verified:', receipt.verifiedSha256);
const msg1 = await server.recv(2000); // 'reflex'
const msg2 = await server.recv(2000); // 'json' -> msg2.asJson()
await server.sendBytes(msg2.peerAddr, 'config-updated');
console.log('Reply from server:', (await client.recv(2000)).asText());
await client.close();
await server.close();
Browser / WebRTC DataChannel Connection (nltp/core):
import { NLTPEndpoint } from 'nltp/core';
// Attach NLTP to any unreliable/unordered WebRTC RTCDataChannel or custom byte link
const endpoint = new NLTPEndpoint({
psk: sharedKey,
sendPacketFn: (packetBytes) => dataChannel.send(packetBytes),
});
dataChannel.binaryType = 'arraybuffer';
dataChannel.onmessage = (ev) => endpoint.onDatagram(new Uint8Array(ev.data), 'peer');
setInterval(() => endpoint.step(), 5); // Drive pacing & FEC timers
endpoint.startTransfer('peer', new Uint8Array(65536), { kind: 'file', filename: 'snapshot.bin' });
const received = await endpoint.waitInbox(5000);
4.3 Java 11+ (io.necklink:nltp)
How it works: Pure Java 11 (--release 11) with zero external Maven/Gradle dependencies. Uses a non-blocking java.nio.channels.DatagramChannel + Selector background loop, portable constant-time RFC 8439 ChaCha20-Poly1305, and thread-safe queues.
mvn -f packages/nltp/java/pom.xml package # Produces packages/nltp/java/target/nltp-1.3.2.jar
Basic Java Connection:
import io.necklink.nltp.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
public class Example {
public static void main(String[] args) throws Exception {
String key = NLTPWire.generateNltpKey();
try (NLTPServer server = NLTPServer.builder()
.bind("0.0.0.0", 9000)
.psk(key)
.saveDir(Paths.get("./inbox"))
.build();
NLTPClient client = NLTPClient.builder()
.server("127.0.0.1", 9000)
.psk(key)
.maxTxKbps(20_000.0)
.build()) {
// 1. Confirmed priority reflex
int seq = client.sendReflex("RESET_COUNTER");
boolean acked = client.waitReflexAck(seq, 3000L);
// 2. FEC-protected, SHA-256 verified transfer
TransferReceipt receipt = client.sendBytes(
"telemetry-payload".getBytes(StandardCharsets.UTF_8), "sample.bin", 10_000L);
System.out.println("Verified: " + receipt.verifiedSha256());
// 3. Receive on server and reply
ReceivedItem<?> reflex = server.recv(2000L);
ReceivedItem<?> file = server.recv(2000L);
server.sendBytes((java.net.InetSocketAddress) file.peerAddr(),
"received-ok".getBytes(StandardCharsets.UTF_8), 5_000L);
}
}
}
4.4 Android (io.necklink:nltp, API 26+)
How it works: Because io.necklink:nltp compiles to zero-dependency Java 11 bytecode using standard java.nio.channels.DatagramChannel and ships its own ChaCha20-Poly1305 implementation (no native JNI .so libraries or third-party dependencies), it drops directly into any Kotlin or Java Android app (minSdk = 26, Android 8.0+):
- Direct Local Link: Connect an Android phone directly to an
ESP32over local Wi-Fi or phone hotspot with zero cloud/broker setup. - Remote Cellular Link: Connect from Android 4G/5G to a remote server or
ESP32viaNLTPRelayHub(relay(InetSocketAddress(relayIp, 9000), "device-01")). - Seamless Wi-Fi ↔ 5G Handoff: Call
client.rebindSocket("0.0.0.0", 0)on network changes to migrate an active transfer in0-RTTwithout losing progress.
1. Gradle & Manifest Setup:
Copy nltp-1.3.2.jar into app/libs/ and add to app/build.gradle.kts:
android {
defaultConfig { minSdk = 26 }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
implementation(files("libs/nltp-1.3.2.jar"))
}
Add network permission in AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
2. Basic Android Kotlin Connection (on Dispatchers.IO):
import io.necklink.nltp.NLTPClient
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun syncWithDevice(deviceIp: String, sharedKey: String, configBytes: ByteArray): String? =
withContext(Dispatchers.IO) {
// For CGNAT relay instead of LAN IP, use: .relay(InetSocketAddress(relayIp, 9000), "device-01")
NLTPClient.builder()
.server(deviceIp, 9700)
.psk(sharedKey)
.maxTxKbps(10_000.0)
.build()
.use { client ->
// Send urgent command + verified configuration payload to ESP32 or backend server
val seq = client.sendReflex("SYNC_NOW")
client.waitReflexAck(seq, 2_000L)
val receipt = client.sendBytes(configBytes, "config.bin", 15_000L)
if (!receipt.verifiedSha256()) return@use null
// Wait for two-way reply from device/server
client.recv(5_000L)?.asText()
}
}
4.5 ESP (ESP32 & ESP8266 — Arduino IDE, PlatformIO & ESP-IDF C99)
How it works: The C99 engine (csrc/nltp_core.{h,c}, nltp_sha256.{h,c}, nltp_net.{h,c}) uses zero dynamic heap allocation inside the protocol core and zero floating-point math. On Arduino/PlatformIO, #include <NLTP.h> provides two non-blocking classes driven by calling .poll() inside loop():
NLTPWiFiNode— UDP over Wi-Fi / Ethernet / 4G router (works on all Arduino-ESP32 cores).NLTPESPNowNode— Routerless 2.4 GHz ESP-NOW v2/v1 (Arduino-ESP32 core 3.x / ESP-IDF 5.x).- For
ESP8266(NodeMCU) or custom bare-metal MCUs (STM32,RP2040), wirenltp_core.cdirectly toesp_now_sendor any byte callback.
Install in PlatformIO (platformio.ini) or Arduino IDE (.ZIP):
; platformio.ini
lib_deps =
symlink://../necklink/packages/nltp/arduino/NLTP
(For Arduino IDE: zip packages/nltp/arduino/NLTP and add via Sketch → Include Library → Add .ZIP Library...)
Example A: ESP32 over Wi-Fi UDP (Connects Directly to Python / JS / Java / Android)
#include <WiFi.h>
#include <NLTP.h>
NLTPWiFiNode nltp;
uint8_t samplePayload[2048];
void setup() {
WiFi.begin("YOUR_WIFI_SSID", "YOUR_WIFI_PASSWORD");
while (WiFi.status() != WL_CONNECTED) delay(200);
nltp.begin(9700, "shared-256bit-key-or-passphrase");
nltp.setPeer(IPAddress(192, 168, 1, 100), 9000); // IP/port of Python, JS, Java, or Android peer
}
void loop() {
nltp.poll(); // Drives TX pacing, Cauchy FEC repairs, and RX decoding
if (nltp.hasReceivedFile() && nltp.isReceivedShaOk()) {
// Access verified incoming payload via nltp.receivedData() & nltp.receivedLength()
nltp.clearReceived();
nltp.sendBytes(samplePayload, sizeof(samplePayload), "sensor_batch.bin");
}
}
(Behind a 4G/5G SIM router with no public IP? Replace begin() + setPeer() with one line:)
nltp.beginRelay(9700, IPAddress(203, 0, 113, 10), 9000, "device-01", "shared-256bit-key-or-passphrase");
Example B: ESP32 ↔ ESP32 Routerless ESP-NOW (No Wi-Fi Router Needed)
#include <NLTP.h> // Requires Arduino-ESP32 core 3.x (ESP-IDF 5.x)
NLTPESPNowNode node;
const uint8_t peerMac[6] = {0xAA, 0xBB, 0xCC, 0x00, 0x00, 0x02}; // Target ESP32 STA MAC
uint8_t dataBatch[8192];
void setup() {
node.begin(11 /* 2.4 GHz channel 1-14 */, "shared-256bit-key-or-passphrase", NLTP_ESPNOW_RATE_AUTO);
// node.setEspNowV1Mode(true); // Enable ONLY when sending to an ESP8266 or 250-byte v1 peer
node.setPeerMac(peerMac);
node.sendBytes(dataBatch, sizeof(dataBatch), "batch.bin"); // Keep buffer valid until isSendComplete()
}
void loop() {
node.poll();
if (node.hasReceivedFile() && node.isReceivedShaOk()) {
// Verified multi-KB file received over raw 2.4 GHz ESP-NOW!
node.clearReceived();
}
}
Example C: ESP8266 (NodeMCU) Cross-Chip ESP-NOW v1 (250 B MTU)
On ESP8266 (80 KB SRAM), compile the portable C99 core with 192 B shards (242 B encrypted L2 frame <= 250 B v1 limit) — any ESP32 running NLTPESPNowNode or nltp_espnow_hub_t decodes its packets automatically:
#define NLTP_SHARD_BYTES 192
#define NLTP_RX_SLOTS 2
#define NLTP_RX_MAX_GENS 48
#include <espnow.h>
#include "nltp_core.h"
static void sendFrame(void * /*user*/, const uint8_t *pkt, size_t len) {
esp_now_send(peerMac, (uint8_t *)pkt, (int)len); // Sends 242 B encrypted frame to ESP32 or ESP8266
}
(Full working sketch: esp32_mesh/mesh3_nodemcu8266/mesh3_nodemcu8266.ino.)
Example D: ESP-NOW → Wi-Fi UDP Gateway Bridge
Run both classes on one ESP32 (setting espNow.begin(0, ...) so ESP-NOW stays locked to the Wi-Fi router's channel):
// Inside loop(): receive verified batches from off-Wi-Fi ESP-NOW sensors and forward over UDP/4G
espNow.poll();
wifiUdp.poll();
if (espNow.hasReceivedFile() && espNow.isReceivedShaOk() && wifiUdp.isSendComplete()) {
memcpy(forwardBuf, espNow.receivedData(), espNow.receivedLength());
wifiUdp.sendBytes(forwardBuf, espNow.receivedLength(), espNow.receivedFilename());
espNow.clearReceived();
}
(See arduino/NLTP/examples/ESP32_ESPNow_Gateway for a complete gateway sketch with retry handling, and esp32_mesh/ for multi-peer C99 nltp_espnow_hub_t firmware.)
5. Protocol Comparison: NLTP vs. MQTT vs. Raw ESP-NOW vs. TCP / QUIC
| Capability | NLTP (v1.3.2) |
MQTT (over TCP/TLS) | Raw ESP-NOW (v1 / v2) |
TCP BBR / QUIC |
|---|---|---|---|---|
| Underlying Transport | UDP/IP, CGNAT Relay, WebRTC, or L2 ESP-NOW | TCP/IP (or QUIC in niche brokers) | IEEE 802.11 L2 vendor action frames only | TCP/IP or UDP/IP (QUIC) |
| Central Broker Needed? | No — direct P2P, hub, or optional 1-port relay | Yes — requires an MQTT broker (Mosquitto, EMQX, AWS IoT) | No — direct peer MAC to MAC | No — direct client/server |
| Works Without Wi-Fi Router? | Yes (ESP32 & ESP8266 via ESP-NOW) |
No — requires full IP + TCP stack | Yes (ESP32 & ESP8266) |
No — requires IP stack |
| Crosses Internet & 4G CGNAT? | Yes (built-in STUN punch + zero-key relay) | Yes (if broker is hosted on a public IP) | No — single 2.4 GHz RF hop only | Yes (requires public IP or separate TURN/VPN) |
| 10–35% Packet Loss Behaviour | Forward heals via GF(2^8) Cauchy FEC (100% SHA-256 @ 35% loss) |
TCP head-of-line stalls, broker keepalive drops & reconnect storms | 0% multi-KB file delivery from 10%+ loss (no chunk repair) |
Retransmission backoff (2–7× slower on bursty fades) |
| Urgent Command During Upload | send_reflex() bypasses queue (0 HOL blocking) |
Blocked behind in-flight TCP socket send buffer | Immediate single frame (unconfirmed at app layer) | TCP: blocked; QUIC: multi-stream capable |
| Payload Size & Integrity | Multi-MB files & batches + mandatory SHA-256 receipt |
Up to 256 MB messages (broker-limited, no built-in file hash) |
Single 250 B (v1) or 1,470 B (v2) frame only |
Arbitrary byte stream (TLS MAC, no file receipt) |
| Encryption & Peer Limit | ChaCha20-Poly1305 (256-bit) — unlimited peers |
TLS 1.2/1.3 (~40–60 KB RAM on MCU) |
AES-128 CCMP capped at 6–17 encrypted peers |
TLS 1.3 (QUIC too heavy for 80–320 KB MCUs) |
Pub/Sub Topic Routing (a/+/c) |
No (transport layer, not a message bus) | Yes — MQTT's core superpower (1-to-10,000 fan-out, QoS, retained state) | No | No |
When to Use NLTP — and When Not To
Use NLTP when:
- Transferring binary batches, sensor logs, audio/image snapshots, configs, or OTA firmware (
1 KB – 10 MB) across lossy (2–35%), bursty, long-RTT, cellular-edge, or congested 2.4 GHz links.- Building routerless
ESP32/ESP8266meshes that need encrypted multi-KB transfers, cross-chipv1 ↔ v2compatibility, or seamless bridging to Python, Node.js, Java, or Android.- Connecting field devices and mobile apps across 4G/5G carrier CGNAT without setting up VPNs, TLS certificates, or heavyweight cloud brokers.
- Combining NLTP + MQTT: Use NLTP on the lossy wireless edge (
ESP8266/ESP32 → Gateway/Cloud Ingest) and publish verified payloads into MQTT inside your cloud backend for dashboard and microservice fan-out.
Stick with standard protocols when:
- Cloud pub/sub fan-out & smart-home JSON topics on clean Wi-Fi: Use MQTT — its topic wildcards, retained messages, and managed brokers are purpose-built for thousands of clean-network subscribers.
- Clean wired/Wi-Fi links or large (
>= 10 MB) steady-loss transfers between servers: KernelTCP BBRandQUICare12–27%faster on clean links and send fewer wire bytes (1.04–1.12xvs. NLTP's1.20–1.54xFEC parity overhead).- Coin-cell
<= 250 Bfire-and-forget sensor pings: RawESP-NOWwakes and transmits a single frame in~1–3 mswith zero session state when occasional dropped readings are acceptable.
6. Benchmarks & Hardware Verification
All raw JSON logs and reproducible test harnesses live in results/ and scripts/.
6.1 Off-Wi-Fi ESP32 ↔ ESP32 Hardware: NLTP 1.3.2 ESP-NOW v2 vs. Raw ESP-NOW v2
Measured live on two physical boards (ESP32-D0WDQ6 ↔ ESP32-D0WD-V3, Channel 11, wifi_sta=0, ip=0.0.0.0, 1,470 B ESP-NOW v2 MTU; results/esp32_mesh3_v2_vs_raw_v2.json):
A. 16 KB File Delivery Under Controlled Packet Loss (3 Trials / Level)
| Receiver Frame Loss | Raw ESP-NOW v2 (1,470 B chunks, 802.11 L2 ACK only) |
NLTP 1.3.2 over ESP-NOW v2 (ChaCha20-Poly1305 + GF(2^8) FEC + SHA-256) |
|---|---|---|
0% |
3/3 (100%) · 15.11 Mbps |
3/3 (100% SHA-256) · 15.88 Mbps (8.45 ms) |
5% |
3/3 (100%) · 14.98 Mbps (L2 retry limit border) |
3/3 (100% SHA-256) · 14.87 Mbps (9.03 ms) |
10% |
0/3 (0% intact — missing chunks) |
3/3 (100% SHA-256) · 14.49 Mbps (9.26 ms) |
15% |
0/3 (0% intact) |
3/3 (100% SHA-256) · 2.55 Mbps (52.6 ms) |
25% |
0/3 (0% intact) |
3/3 (100% SHA-256) · 4.79 Mbps (28.0 ms) |
35% |
0/3 (0% intact) |
3/3 (100% SHA-256) · 2.27 Mbps (59.1 ms) |
B. Throughput, PHY Modes, Full-Duplex, Soak & Security Highlights (v1.3.2)
- Peak
32 KBEncrypted Goodput:17.75 Mbpsmedian /18.35 Mbpsmax (14.77 ms) with full 256-bitChaCha20-Poly1305AEAD +SHA-256verification (vs.16.96 Mbpsmedian /20.53 Mbpsmax unencrypted Raw ESP-NOW v2). - Small-Payload Latency (
4 B/64 B):2.72 ms(CRC-32) /3.17 ms(4 BAEAD) /2.62 ms(64 BAEAD) vs.3.05–3.06 msRaw ESP-NOW v2. - All 7 Hardware 2.4 GHz PHY Modes Verified (
8 KBAEAD,3/3SHA-256 each):802.11b 1M:0.45 Mbps·802.11b 11M:7.96 Mbps·802.11n MCS4:5.82 Mbpsmed (14.90max) ·MCS7:7.11 Mbpsmed (17.24max) ·AUTO:7.19 Mbpsmed (16.16max) · Espressif Long-RangeLR 250K:0.10 Mbps·LR 500K:0.26 Mbps.
- Simultaneous Full-Duplex Collision Stress (
32 KB A→B+32 KB B→Aat once):5/5(100%) at0%loss (3.49 Mbps) and5/5(100%) at10%loss (3.05 Mbps, where Raw v2 scored0/5). - Zero-Leak Endurance & Deep-Sleep Wake:
50 × 8 KBback-to-back transfers (400 KB,50/50SHA-256 in13.16 s) with+4 BTX /0 BRX heap delta, plus20/20freshconn_idcold wakes in4.34 msmedian. - Anti-Stall & Adversarial Security (
160/160Blocked):3/3(100%) survival when authenticated stray-conn_idframes were injected mid-transfer at10%loss (v1.3.21-second takeover guard).80/80forged frames from 25 random spoofed MACs rejected pre-auth with0hub slots allocated +80/80bit-flipped, replayed, wrong-key, and plaintext-downgrade frames dropped (whereas Raw ESP-NOW v2 accepted40/40spoofed frames as valid data).
6.2 Live 3-Board Cross-Chip Off-Wi-Fi Mesh (ESP32 + ESP8266EX NodeMCU + ESP32)
Measured across three heterogeneous physical boards on Channel 11 with zero Wi-Fi router (wifi_sta=0, ip=0.0.0.0; results/esp32_esp8266_crosschip_mesh.json):
- Node 0:
ESP32-D0WDQ6(520 KBSRAM,ESP-NOW v21,470 BMTU) - Node 1:
ESP8266EX NodeMCU(80 KBSRAM,22 KBfree heap,ESP-NOW v1250 BMTU,192 BNLTP shards) - Node 2:
ESP32-D0WD-V3(520 KBSRAM,ESP-NOW v21,470 BMTU, 2-slot multi-peer hub)
| Cross-Chip / Multi-Board Test | Payload & Conditions | Raw ESP-NOW | NLTP 1.3.2 Cross-Chip Mesh |
|---|---|---|---|
| All 6 Pairwise Directional Links | 16 KB (ESP32↔ESP32) & 8 KB (ESP8266↔ESP32) @ 0% loss |
ESP32↔ESP32: 3/3ESP8266↔ESP32: 0/3 |
18/18 (100% SHA-256) · up to 12.23 Mbps (ESP32↔ESP32) & 7.25 Mbps (NodeMCU→ESP32) |
Cross-Chip Loss Sweep (NodeMCU → ESP32) |
8 KB encrypted (242 B L2 frames) @ 0%, 10%, 25%, 35% loss |
0/3 at every level |
3/3 (100% SHA-256) at all 4 loss levels (2.10, 1.03, 2.45, 1.00 Mbps) |
2-Hop Relay via ESP32 |
NodeMCU (v1) → ESP32 Relay → ESP32 Hub (8 KB, 0% & 15% loss) |
No multi-hop recovery | 3/3 (100% SHA-256) · 1.32 Mbps (0%) & 1.07 Mbps (15% loss) |
2-Hop Relay Through 250 B ESP8266 |
ESP32 → NodeMCU 250 B Relay → ESP32 Hub (8 KB, 0% & 15% loss) |
0/3 (0% intact) |
3/3 (100% SHA-256) · 2.44 Mbps (0%) & 0.54 Mbps (15% loss) |
| Simultaneous Dual-Sender Mixed Hub | ESP32 (32 KB v2) + NodeMCU (8 KB v1) → ESP32 Hub at the same instant |
0/5 (0% both intact) |
5/5 (100% both SHA-256) @ 0% (365 ms) & 5/5 @ 10% loss (380 ms) |
6.3 ESP32 Wi-Fi UDP (ESP32 ↔ Router ↔ PC) Across Degraded Links
Measured over 2 Wi-Fi hops (ESP32-D0WDQ6 ↔ Router ↔ PC) on 58–60 KB encrypted files with SHA-256 verification (results/esp32_espnow_vs_nltp.json):
- Clean Desk Wi-Fi (
0%loss):2.57–3.04 Mbps(162–191 ms) vs.2.33 Mbps(211 ms) on lwIPTCP. - Weak Wi-Fi (
5 Mbit,5%loss):1.90–1.98 Mbps(12/12verified). - Degraded Cellular Profile (
2 Mbit,9–13%loss):0.56–0.75 Mbps(12/12verified). - Narrowband IP Radio (
1 Mbit,6–18%loss):0.31–0.53 Mbps(12/12verified). - Severe Packet Loss (
1 Mbit,20.8%loss):0.17–0.28 Mbps(12/12verified).
6.4 Linux tc netem 1 MB, 10 MB & Satellite Benchmarks vs. 7 Protocols
Measured on Linux 6.18 network namespaces (tc netem + tbf, symmetric loss and rate limits in both directions, iptables wire-byte ratio in parentheses):
Emulated Symmetric Link (1 MB File) |
NLTP |
TCP BBR |
QUIC (ngtcp2) |
KCP (w128) |
kcptun (FEC) |
UDT4 |
SRT |
|---|---|---|---|---|---|---|---|
Clean (20 Mbit, 20 ms, 0%) |
0.58 s (1.23x) |
0.51 s (1.04x) |
0.57 s (1.05x) |
0.60 s (1.54x) |
0.74 s (2.25x) |
0.95 s (1.15x) |
0.87 s (1.15x) |
Light Loss (20 Mbit, 40 ms, 2%) |
0.60 s (1.20x) |
0.65 s (1.07x) |
0.67 s (1.07x) |
0.85 s (1.25x) |
0.81 s (1.62x) |
3.02 s (1.07x) |
1.37 s (1.05x) |
Moderate Loss (10 Mbit, 100 ms, 5%) |
1.28 s (1.30x) |
1.49 s (1.12x) |
1.57 s (1.07x) |
1.74 s (1.45x) |
1.52 s (1.75x) |
9.98 s (1.19x) |
6.49 s (1.24x) |
Heavy Loss (5 Mbit, 200 ms, 10%) |
2.78 s (1.41x) |
3.45 s (1.15x) |
4.42 s (1.16x) |
4.08 s (1.61x) |
3.15 s (1.72x) |
26.47 s (1.37x) |
14.73 s (1/3) |
Severe Loss (2 Mbit, 300 ms, 20%) |
6.36 s (1.54x) |
8.31 s (1.32x) |
11.87 s (1.26x) |
8.98 s (2.11x) |
9.38 s (2.25x) |
FAIL (0/3) |
FAIL (0/3) |
Bursty Fades (10 Mbit, 100 ms, 10% burst) |
1.84 s (1.49x) |
13.10 s (2/3) |
3.48 s (CUBIC) |
2.16 s (1.63x) |
2.07 s (1.71x) |
9.55 s (1.34x) |
4.11 s (2/3) |
Additional link profiles:
- GEO Satellite (
2 Mbit,600 msRTT,3%loss):0.91 son64 KB(vs.2.77 sTCP BBR/2.67 sQUIC) and6.05 son1 MB(vs.8.85 sTCP BBR/7.29 sQUIC). 10 MBBursty Fades (10 Mbit,100 ms,10%burst):13.79 s(3/3passed) vs.TCP BBR(1/3passed) andQUIC(1/3passed).- Where
TCP BBR/QUIC/kcptunWin: On10 MBclean (4.45 sTCP BBRvs.5.64 sNLTP),10 MBsteady5%loss (9.81 sTCP BBRvs.11.28 sNLTP), and heavy±20 msjitter (1.39 skcptun/1.52 sQUICvs.3.28 sNLTP).
6.5 216/216 4-Language Wire Interop & 18/18 Dual-NAT / CGNAT Suite
216/216Cross-Language Matrix (scripts/multilang_interop_test.py):192/192full 4×4 directional matrix (Python ↔ C99 ↔ JS ↔ Java) across plaintext, Base64 256-bit keys, and passphrases at0%and15%packet loss (1 KBand64 KB), plus48/48192 BESP-NOW v1 shard interop (1 KB–300 KB).8/8256 KBlossy cross-language streams +6/6cross-language zero-knowledgeNLTPRelayHubrooms +10/10reliable out-of-bandsend_reflex+wait_reflex_ackat15%loss (300/300at30%loss in unit suites).
18/18Dual-NAT & Symmetric-CGNAT Suite (scripts/virtual_nat_test.py):9/9direct STUN +TTL=2UDP hole-punching across two separate Linux NAT routers (1 MBin1.22–2.26 sat5%last-mile loss).- Automatic 3-second fallback to
NLTPRelayHubunder symmetric CGNAT (MASQUERADE --random-fully,1.80 s) and NAT mapping preservation across idle gaps.
7. Repository Layout & Testing
| Path | Contents |
|---|---|
necklink/ & nltp/ |
Python SDK & import nltp alias (NLTPServer, NLTPClient, NLTPEndpoint, NLTPRelayHub, STUN, nltp CLI) |
js/ |
Zero-dependency JavaScript / TypeScript SDK (ESM + CommonJS + .d.ts + Browser/WebRTC nltp/core + Node UDP + nltp-js CLI) |
java/ |
Zero-dependency Java 11+ & Android SDK (io.necklink:nltp, Maven pom.xml, NIO UDP, portable ChaCha20-Poly1305, NLTPCli) |
arduino/NLTP/ |
Arduino IDE & PlatformIO library (#include <NLTP.h>): NLTPWiFiNode (UDP) & NLTPESPNowNode (ESP-NOW), plus 6 example sketches |
csrc/ |
Zero-heap, zero-float ANSI C99 core (nltp_core.{h,c}, nltp_net.{h,c}, nltp_sha256.{h,c}, nltp_espnow.h), POSIX CLI & host simulator |
esp32/ & esp32_mesh/ |
Hardware firmware for ESP32 Wi-Fi UDP, ESP32 ESP-NOW v2 multi-peer hubs, and ESP8266EX (NodeMCU) cross-chip v1 mesh nodes |
examples/ |
End-to-end runnable Python, Node.js, Java, and Arduino examples (examples/README.md) |
scripts/ & results/ |
Multi-language interop, live field-test tool, NAT harnesses, and hardware JSON benchmark logs |
tests/ |
pytest unit and integration suite |
Run Verification Suites Locally
# Python unit & integration tests
pytest
# C99 self-tests (crypto, FEC, tampering, replay, stream takeover, ESP-NOW v1) + simulated radio hub
make -C csrc all test espnow-sim
# JavaScript & Java self-tests
npm --prefix js test
mvn -f java/pom.xml test
# Full 216/216 4-language interoperability suite
python scripts/multilang_interop_test.py
# Benchmark NLTP vs. kernel TCP over any real Wi-Fi, cellular, or radio link between two hosts
python scripts/field_test.py server --port 9700 --psk "<KEY>"
python scripts/field_test.py client --host <SERVER_IP> --port 9700 --psk "<KEY>" --sizes 64k,256k,1m --runs 5
8. Changelog
1.3.2 (Python, ANSI C99, Arduino/ESP, JavaScript/TypeScript, Java/Android)
- Confirmed Priority Reflex Lane (
PT_REFLEX_ACK,0x0A):send_reflex/sendReflexnow retransmits (15–250 msbackoff,3 swindow) until acknowledged, deduplicates by(conn_id, seq), and addswait_reflex_ack/waitReflexAckplus server-to-clientNLTPServer.send_reflex(client_addr, msg)(300/300verified at15%and30%bidirectional loss across Python, JS, and Java). - C99 & ESP Anti-Stall Takeover Guard: Prevents a delayed replay or stray
conn_idpacket from resetting an in-progress receive stream unless the stream finishes or has been idle for>= 1,000 ms(verified100%on physicalESP32hardware under mid-stream injection while preserving4.34 msdeep-sleep cold wakes). - Hardened Multi-Peer ESP-NOW Hub (
nltp_espnow.h): Pre-authenticates unknown senders before allocating slots (80/80spoofed MAC frames blocked with0slots used), protects active transfers from eviction, adds atomic TX in-flight tracking, and LRU-manages the 20-entry ESP-NOW peer table. - Hardware Verification (
ESP32v2 +ESP8266EXCross-Chip Mesh): Verified off-Wi-Fi on physicalESP32(17.75 Mbpsmedian /18.35 Mbpsmax on32 KBAEAD,100%SHA-256across0–35%packet loss and all 7 PHY modes includingLR 250K/500K) and across a 3-boardESP32 + ESP8266EX NodeMCU + ESP32mesh (100%SHA-256across all 6 cross-chip links, 2-hop wireless relays through250 BESP8266, and simultaneousv2 + v1multi-sender hub reception). - Fixes & Compatibility:
recv(timeout=0)non-blocking poll fix in Python; Arduino-ESP32 core 2.x compile compatibility restored forNLTPWiFiNode; unified1.3.2version across all five SDKs.
1.2.1 – 1.2.3 (C99 / Arduino): Added nltp_espnow.h and NLTPESPNowNode (1,470 B v2 and 250 B v1 modes) with automatic per-packet shard-width detection on receive.
License
Released under the MIT License. Part of the open-source NeckLink engineering suite.
Metadata
Release files for pynltp 1.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pynltp-1.3.2.tar.gz | 125.9 kB | Without provenance | Details for pynltp-1.3.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pynltp-1.3.2-py3-none-any.whl | Python 3 | none | any | Details for pynltp-1.3.2-py3-none-any.whl |
Total release size: 224.4 kB
Release files / pynltp-1.3.2.tar.gz
| Download URL | pynltp-1.3.2.tar.gz |
|---|---|
| Size | 125.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
388970a6afa9b8079b2958ab03d500b5986507a6d996e57a735c4f0fae166ab0
|
|
BLAKE2b-256 checksum How to use checksums |
f89bc774ad8af38e1735676e69809d40417b948a5482d27745fc9333acfe2feb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / pynltp-1.3.2-py3-none-any.whl
| Download URL | pynltp-1.3.2-py3-none-any.whl |
|---|---|
| Size | 98.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
70cfadb52ef7a234e966ebfb719effa651b06f22594680019065af0c7e1098e4
|
|
BLAKE2b-256 checksum How to use checksums |
6aacbb065daeb4f71b879660246697aa6ced748bcdce3e00a99ac32420546c6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Security for pynltp 1.3.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.