Skip to content

venomyzer/ZeroLink

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

███████╗███████╗██████╗  ██████╗ ██╗     ██╗███╗   ██╗██╗  ██╗
╚══███╔╝██╔════╝██╔══██╗██╔═══██╗██║     ██║████╗  ██║██║ ██╔╝
  ███╔╝ █████╗  ██████╔╝██║   ██║██║     ██║██╔██╗ ██║█████╔╝ 
 ███╔╝  ██╔══╝  ██╔══██╗██║   ██║██║     ██║██║╚██╗██║██╔═██╗ 
███████╗███████╗██║  ██║╚██████╔╝███████╗██║██║ ╚████║██║  ██╗
╚══════╝╚══════╝╚═╝  ╚═╝ ╚═════╝ ╚══════╝╚═╝╚═╝  ╚═══╝╚═╝  ╚═╝

Your devices. Your network. Zero cloud.

Status Platform Java Spring Boot License


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.


Motivate me to complete this project

ko-fi


🚧 Under Construction

╔══════════════════════════════════════════════════════════════╗
║                                                              ║
║         ️  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.           ║
║                                                              ║
╚══════════════════════════════════════════════════════════════╝

📖 Table of Contents


💡 What is ZeroLink?

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.


✨ Features

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

🏗️ System Architecture

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) ◄──────────────────►            │
└─────────────────────────────────────────────────────────────────────┘

Core Design Principles

  • 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

🔍 Discovery Flow

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 Flow

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.


🔐 Auth Flow

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.


📁 File Transfer Flow (Coming Soon)

  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"

📋 Clipboard Sync Flow (Coming Soon)

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 ✓

📁 Project Structure

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

🛠️ Tech Stack

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

🚀 Getting Started

Prerequisites

  • Java 21+
  • Gradle
  • Both devices on the same WiFi network

Setup

1. Clone the repo:

git clone https://github.com/venomyzer/zerolink-backend.git
cd zerolink-backend

2. Set your device name in application.properties:

zerolink.device-name=Bibek-MacBook   # change this per device

3. Run:

./gradlew bootRun        # macOS / Linux
gradlew.bat bootRun      # Windows

The server starts on http://localhost:7465

Windows Firewall

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

Pairing Two Devices

# 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>

📡 API Reference

Device Endpoints (public)

Method Endpoint Description
GET /api/devices/me This device's identity
GET /api/devices/peers All discovered peers

Pairing Endpoints (public)

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

Protected Endpoints (require headers)

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)

🗺️ Roadmap

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

👨‍💻 Author

Bibek Das@venomyzer

Building this one milestone at a time. No cloud. No shortcuts.


ZeroLinkBecause your devices should talk to each other, not to a server farm.

ko-fi

About

ZeroLink — A cross-platform LAN-based peer-to-peer ecosystem enabling real-time file transfer and clipboard synchronization across devices without cloud dependency.

Topics

Resources

License

Stars

Watchers

Forks

Packages