███████╗███████╗██████╗ ██████╗ ██╗ ██╗███╗ ██╗██╗ ██╗
╚══███╔╝██╔════╝██╔══██╗██╔═══██╗██║ ██║████╗ ██║██║ ██╔╝
███╔╝ █████╗ ██████╔╝██║ ██║██║ ██║██╔██╗ ██║█████╔╝
███╔╝ ██╔══╝ ██╔══██╗██║ ██║██║ ██║██║╚██╗██║██╔═██╗
███████╗███████╗██║ ██║╚██████╔╝███████╗██║██║ ╚████║██║ ██╗
╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝
ZeroLink is a LAN-based cross-platform ecosystem — built from scratch.
File transfer. Clipboard sync. Device pairing. No internet. No cloud. No compromises.
Think AirDrop, but for everyone.
╔══════════════════════════════════════════════════════════════╗
║ ║
║ ️ THIS PROJECT IS ACTIVELY BEING BUILT ║
║ ║
║ Stage 1 ██████████████████░░░░░░░ 70% In Progress ║
║ Stage 2 ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% Not Started ║
║ Stage 3 ░░░░░░░░░░░░░░░░░░░░░░░░░ 0% Not Started ║
║ ║
║ Expect rough edges. Breaking changes. Late nights. ║
║ This is a real project being built step by step. ║
║ ║
╚══════════════════════════════════════════════════════════════╝
- What is ZeroLink?
- Features
- System Architecture
- Discovery Flow
- Pairing Flow
- Auth Flow
- File Transfer Flow
- Clipboard Sync Flow
- Project Structure
- Tech Stack
- Getting Started
- API Reference
- Roadmap
ZeroLink is a peer-to-peer LAN ecosystem that lets your devices talk to each other directly over your local WiFi — no internet, no cloud, no third-party servers involved.
Every device runs a lightweight Spring Boot server. Devices discover each other using mDNS (Bonjour/Zeroconf), authenticate using a PIN-based pairing flow, and communicate over direct HTTP + WebSocket connections.
It's like building Apple's ecosystem from scratch — but open, cross-platform, and fully under your control.
| Feature | Status | Description |
|---|---|---|
| 🔍 Device Discovery | ✅ Done | Auto-discover devices on the same WiFi via mDNS |
| 🤝 Device Pairing | ✅ Done | PIN-based trust establishment between devices |
| 🔐 Token Auth | ✅ Done | All communication requires a shared pairing token |
| 💾 Persistent Pairing | ✅ Done | Pairing survives restarts — stored on disk |
| 📁 File Transfer | 🚧 In Progress | Send & receive files over LAN |
| 📋 Clipboard Sync | 🔜 Coming Soon | Copy on one device, paste on another |
| 🖥️ React UI | 🔜 Coming Soon | Clean local web interface |
Each device in the ZeroLink ecosystem runs an identical Spring Boot server. There is no master, no coordinator — every device is both a client and a server simultaneously.
┌─────────────────────────────────────────────────────────────────────┐
│ LOCAL WIFI NETWORK │
│ 192.168.x.x/24 │
│ │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ DEVICE A (Mac) │ │ DEVICE B (Windows) │ │
│ │ │ │ │ │
│ │ ┌───────────────────┐ │ │ ┌───────────────────┐ │ │
│ │ │ React UI │ │ │ │ React UI │ │ │
│ │ │ (port 7465) │ │ │ │ (port 7465) │ │ │
│ │ └────────┬──────────┘ │ │ └────────┬──────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────────▼───────────┐ │ │ ┌────────▼───────────┐ │ │
│ │ │ Spring Boot Core │ │ │ │ Spring Boot Core │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ REST WebSocket │◄─┼─────┼─►│ REST WebSocket │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ ┌──────────────┐ │ │ │ │ ┌──────────────┐ │ │ │
│ │ │ │PairingService│ │ │ │ │ │PairingService│ │ │ │
│ │ │ │FileTransfer │ │ │ │ │ │FileTransfer │ │ │ │
│ │ │ │Clipboard │ │ │ │ │ │Clipboard │ │ │ │
│ │ │ └──────────────┘ │ │ │ │ └──────────────┘ │ │ │
│ │ └────────┬───────────┘ │ │ └────────┬───────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────────▼──────────┐ │ │ ┌────────▼──────────┐ │ │
│ │ │ JmDNS (mDNS) │ │ │ │ JmDNS (mDNS) │ │ │
│ │ │ _zerolink._tcp. │ │ │ │ _zerolink._tcp. │ │ │
│ │ └───────────────────┘ │ │ └───────────────────┘ │ │
│ └──────────────────────────┘ └──────────────────────────┘ │
│ │
│ mDNS Multicast (UDP 5353) ◄──────────────────► │
│ Direct HTTP/WS (TCP 7465) ◄──────────────────► │
└─────────────────────────────────────────────────────────────────────┘
- Symmetric — every device runs the same code, same server, same port
- Zero cloud — all communication stays within your LAN
- Trust-first — devices must explicitly pair before any data flows
- Resilient — device identity and pairing tokens survive restarts
How devices find each other automatically using mDNS (Multicast DNS — the same protocol Apple uses for Bonjour).
Device A starts up
│
▼
JmDNS registers service on LAN
"_zerolink._tcp.local." at 192.168.1.2:7465
TXT record: { deviceId, deviceName }
│
│ (multicast UDP broadcast)
│
▼
Device B receives the announcement
│
▼
serviceAdded() fires → requestServiceInfo()
│
▼
serviceResolved() fires with full details
(IP, port, TXT metadata)
│
▼
Parse deviceId + deviceName from TXT record
│
├── Is it ourselves? → SKIP
├── Already known? → UPDATE IP only
└── New device? → ADD to discoveredDevices map
│
▼
GET /api/devices/peers now shows the device
Real-world analogy: mDNS is like a store putting a sign in the window. Every device on the network can see the sign without anyone needing to know the store's address in advance.
Pairing establishes mutual trust between two specific devices. Unpaired devices on the same network are completely ignored.
DEVICE A (initiator) DEVICE B (receiver)
│ │
│ POST /api/pair/request │
│ { deviceId, deviceName, │
│ ipAddress, port } │
│ ────────────────────────────────►│
│ │
│ Generate 6-digit PIN
│ Store in pendingPins map
│ Log PIN to console
│ │
│◄──────────────────────────────── │
│ { success: true, │
│ message: "PIN: 482910" } │
│ │
│ [User reads PIN from │
│ Device B's console/UI] │
│ │
│ POST /api/pair/confirm │
│ ?deviceId=...&pin=482910 │
│ ────────────────────────────────►│
│ │
│ PIN matches?
│ YES → Generate shared UUID token
│ Mark Device A as paired
│ Save to disk
│ │
│◄──────────────────────────────── │
│ { success: true, │
│ token: "7ad34d28-..." } │
│ │
│ POST /api/pair/complete │
│ (on ITSELF) │
│ ?deviceId=B&token=7ad34d28-... │
│ │
│ Mark Device B as paired ✓ │
│ Save to disk ✓ │
│ │
✅ BOTH DEVICES NOW TRUST EACH OTHER
Real-world analogy: Like pairing a Bluetooth device. One side generates a PIN, the other types it in. Only after both sides confirm does the trust get established.
Every request to a protected endpoint must carry two headers proving the caller is a trusted paired device.
Incoming Request
│
▼
AuthInterceptor.preHandle()
│
├── Path is /api/pair/* or /api/devices/*?
│ │
│ └── YES → ALLOW (public endpoints)
│
└── NO → Check headers:
X-Device-Id: <deviceId>
X-ZeroLink-Token: <sharedToken>
│
▼
PairingService.isTokenValid()
│
┌───────┴───────┐
│ │
VALID INVALID
│ │
▼ ▼
ALLOW ✅ 403 FORBIDDEN ❌
"Not paired or
invalid token"
Real-world analogy: Like a hotel key card. Public areas (lobby) are open. Your room requires your specific key card — no card, no entry.
DEVICE A DEVICE B
│ │
│ POST /api/files/send │
│ Headers: X-Device-Id, X-ZeroLink-Token │
│ Body: multipart/form-data stream │
│ ────────────────────────────────────────►│
│ │
│ AuthInterceptor validates token
│ Stream directly to disk
│ (never fully loaded in RAM)
│ │
│◄──────────────────────────────────────── │
│ { filename, size, path } │
│ │
│ WebSocket push to UI:
│ "File received: photo.jpg"
User copies text on Device A
│
▼
Java AWT Toolkit detects clipboard change
│
▼
WebSocket message → Device B
{ type: "CLIPBOARD", content: "copied text" }
│
▼
Device B writes to its clipboard via AWT Toolkit
│
▼
User pastes on Device B ✓
zerolink-backend/
├── src/main/java/com/zerolink/zerolink_backend/
│ │
│ ├── ZeroLinkApplication.java ← entry point
│ │
│ ├── config/
│ │ ├── AppConfig.java ← device identity, peer registry, persist
│ │ ├── AuthInterceptor.java ← token validation on every request
│ │ └── WebSocketConfig.java ← WebSocket + interceptor registration
│ │
│ ├── discovery/
│ │ ├── MdnsService.java ← mDNS advertise + listen
│ │ └── DeviceController.java ← GET /api/devices/me, /peers
│ │
│ ├── pairing/
│ │ ├── PairingService.java ← PIN generation, token creation, validation
│ │ └── PairingController.java ← POST /api/pair/request, /confirm, /complete
│ │
│ └── model/
│ ├── Device.java ← peer device data model
│ ├── PairingRequest.java ← pairing request payload
│ └── PairingResponse.java ← pairing response payload
│
├── src/main/resources/
│ └── application.properties ← port, device name, mDNS service type
│
└── build.gradle ← Gradle Groovy DSL
| Layer | Technology | Why |
|---|---|---|
| Backend | Java 21 + Spring Boot 3.5 | Runs identically on macOS and Windows |
| Discovery | JmDNS 3.5.9 | Pure-Java mDNS. No native deps, works on both platforms |
| Real-time | Spring WebSocket (raw) | No STOMP broker overhead — P2P doesn't need it |
| File Transfer | Spring Multipart Streaming | Streams to disk, never loads full file into RAM |
| Clipboard | Java AWT Toolkit | Cross-platform clipboard access, built into the JDK |
| Auth | UUID token + Spring Interceptor | Simple, stateless, enforced on every request |
| Build | Gradle Groovy DSL | Standard, fast, familiar |
| Frontend | React + Tailwind CSS (coming) | Served from the same Spring Boot server |
- Java 21+
- Gradle
- Both devices on the same WiFi network
1. Clone the repo:
git clone https://github.com/venomyzer/zerolink-backend.git
cd zerolink-backend2. Set your device name in application.properties:
zerolink.device-name=Bibek-MacBook # change this per device3. Run:
./gradlew bootRun # macOS / Linux
gradlew.bat bootRun # WindowsThe server starts on http://localhost:7465
If devices don't discover each other, allow ZeroLink through the firewall:
# Run as Administrator
netsh advfirewall firewall add rule name="ZeroLink mDNS" protocol=UDP dir=in localport=5353 action=allow
netsh advfirewall firewall add rule name="ZeroLink App" protocol=TCP dir=in localport=7465 action=allow# Step 1 — Device A sends pair request to Device B
POST http://<Device-B-IP>:7465/api/pair/request
{ "deviceId": "...", "deviceName": "...", "ipAddress": "...", "port": 7465 }
# Step 2 — Read PIN from Device B's console, confirm from Device A
POST http://<Device-B-IP>:7465/api/pair/confirm?deviceId=<A-id>&pin=<PIN>
# Step 3 — Device A saves the token on itself
POST http://localhost:7465/api/pair/complete?deviceId=<B-id>&token=<token>| Method | Endpoint | Description |
|---|---|---|
| GET | /api/devices/me |
This device's identity |
| GET | /api/devices/peers |
All discovered peers |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/pair/request |
Send pairing request to a device |
| POST | /api/pair/confirm |
Confirm PIN and receive token |
| POST | /api/pair/complete |
Save received token on your own side |
| GET | /api/pair/status/{deviceId} |
Check if a device is paired |
All requests must include:
X-Device-Id: <your-device-id>
X-ZeroLink-Token: <shared-pairing-token>
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/files/send |
Send a file (coming soon) |
| GET | /api/files/list |
List received files (coming soon) |
| WS | /ws/sync |
WebSocket for clipboard sync (coming soon) |
Stage 1 — Core Infrastructure [IN PROGRESS]
✅ mDNS device discovery
✅ PIN-based pairing
✅ Token authentication
✅ Persistent pairing across restarts
🚧 File transfer (streaming)
🔜 Clipboard sync (WebSocket)
🔜 React UI
Stage 2 — Polish & Reliability [NOT STARTED]
🔜 Local HTTPS (self-signed cert)
🔜 Background service (no terminal needed)
🔜 Auto-launch on startup
🔜 Transfer progress indicators
🔜 Unpair / revoke access
Stage 3 — Extended Features [NOT STARTED]
🔜 Multi-device ecosystem (3+ devices)
🔜 Notification sync
🔜 Cross-device drag and drop
🔜 Linux support
Bibek Das — @venomyzer
Building this one milestone at a time. No cloud. No shortcuts.