A production-ready Swift framework for secure peer-to-peer communication over local networks, featuring automatic device discovery, end-to-end encryption, and robust authentication.
- 🔍 Automatic Device Discovery - mDNS/Bonjour service discovery
- 🔐 End-to-End Encryption - AES-256-GCM with Diffie-Hellman key exchange
- 🛡️ Robust Authentication - PIN, QR code, and trusted device support
- 🔌 Direct TCP Connections - Low-latency peer-to-peer communication
- 🎯 Swift 6 Concurrency - Complete data race safety with actors
- 🧪 Comprehensive Testing - Unit and integration tests included
- 📱 Easy Integration - Clean API with SwiftUI support
- Requirements
- Installation
- Quick Start
- Security
- Advanced Usage
- Sample Apps
- Architecture
- API Reference
- Testing
- Troubleshooting
- Contributing
- iOS 14.0+ / macOS 11.0+
- Swift 6.0+
- Xcode 15.0+
Add WiPeerKit to your project through Xcode:
- File → Add Package Dependencies
- Enter:
https://github.com/yourusername/WiPeerKit.git - Select version and add to your target
Or add to your Package.swift:
dependencies: [
.package(url: "https://git@github.com:vincentjoy/WiPeerKit.git", from: "1.0.1")
]Add these entries to your app's Info.plist:
<key>NSLocalNetworkUsageDescription</key>
<string>This app uses the local network to discover and communicate with nearby devices.</string>
<key>NSBonjourServices</key>
<array>
<string>_wipeerkit._tcp</string>
</array>import WiPeerKit
// Initialize with secure defaults
let peerKit = WiPeerKit()
let connectionManager = try SecureConnectionManager(authMethod: .pin("123456"))
// Set up handlers
peerKit.onDataReceived = { data in
if let message = String(data: data, encoding: .utf8) {
print("Received: \(message)")
}
}
connectionManager.onConnectionRequest = { device in
// Show UI for user approval
print("Connection from: \(device.name)")
print("Fingerprint: \(device.fingerprint)")
return true // or false to reject
}
// Start advertising
peerKit.startAdvertising(serviceName: "My Device")
// Or browse for peers
peerKit.startBrowsing()// 1. Discover peers
peerKit.$discoveredPeers
.sink { peers in
for peer in peers {
print("Found: \(peer.name)")
}
}
.store(in: &cancellables)
// 2. Connect with authentication
if let peer = peerKit.discoveredPeers.first {
do {
// This will trigger PIN verification
try await peerKit.secureConnectWithAuth(
to: peer,
using: connectionManager
)
print("Secure connection established!")
} catch {
print("Connection failed: \(error)")
}
}
// 3. Send encrypted messages
try await peerKit.send(message: "Hello, secure world!")WiPeerKit supports multiple authentication methods to prevent unauthorized connections:
// Initialize with PIN
let connectionManager = try SecureConnectionManager(
authMethod: .pin("425817")
)
// The connecting device must enter this PIN
connectionManager.onPinRequested = {
// Show PIN entry UI
return enteredPin
}// Generate QR code for pairing
let qrData = try connectionManager.generateQRCode()
let qrImage = QRCodeGenerator.generate(from: qrData)
// Scan QR code on other device
let connectionManager = try SecureConnectionManager(
authMethod: .qrCode(scannedData)
)let connectionManager = try SecureConnectionManager(
authMethod: .manualApproval
)
connectionManager.onConnectionRequest = { device in
// Show approval dialog
let alert = UIAlertController(
title: "Connection Request",
message: "\(device.name) wants to connect\nFingerprint: \(device.fingerprint)",
preferredStyle: .alert
)
// ... handle user response
}// First connection uses PIN
// Then save as trusted
if userApprovesDevice {
try connectionManager.addTrustedDevice(device)
}
// Future connections are automatic
let connectionManager = try SecureConnectionManager(
authMethod: .trustedDevices(savedDevices)
)- Algorithm: AES-256-GCM (Authenticated Encryption)
- Key Exchange: ECDH using P-256 curve
- Key Derivation: HKDF-SHA256
- Perfect Forward Secrecy: New keys for each session
- Replay Protection: Timestamp validation
- Always use authentication - Never use the basic connection without authentication
- Verify fingerprints - For sensitive data, manually verify device fingerprints
- Implement timeouts - Set appropriate timeouts for authentication
- Audit connections - Log all connection attempts
- Regular key rotation - For long-lived connections, implement key rotation
// Use app-specific service type for isolation
let customServiceType = "_myapp-v1._tcp"
peerKit.startAdvertising(
serviceName: "Device Name",
serviceType: customServiceType
)let options = WiPeerKit.ConnectionOptions(
requireEncryption: true,
handshakeTimeout: 15.0,
maxMessageSize: 1_048_576 // 1MB
)
try await peerKit.secureConnect(to: peer, options: options)// Send structured data
struct GameState: Codable {
let players: [Player]
let score: Int
}
let gameState = GameState(...)
let data = try JSONEncoder().encode(gameState)
try await peerKit.send(data: data)// Send file in chunks
let fileURL = URL(fileURLWithPath: "large-file.zip")
let fileHandle = try FileHandle(forReadingFrom: fileURL)
let chunkSize = 65536 // 64KB chunks
while let chunk = fileHandle.readData(ofLength: chunkSize), !chunk.isEmpty {
try await peerKit.send(data: chunk)
}// Implement custom authentication
class BiometricAuthenticationMethod: AuthenticationMethod {
func authenticate(device: DeviceIdentity) async throws -> Bool {
let context = LAContext()
return try await withCheckedThrowingContinuation { continuation in
context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Authenticate connection from \(device.name)"
) { success, error in
continuation.resume(returning: success)
}
}
}
}A complete SwiftUI chat application demonstrating all features:
cd Examples/WiPeerKitDemo
open WiPeerKitDemo.xcodeprojFeatures demonstrated:
- Device discovery with visual feedback
- PIN-based pairing flow
- Encrypted messaging
- Connection status indicators
- Trust management
A macOS CLI tool for testing and debugging:
# Build and run
swift run WiPeerKitCLI
# Available commands:
> advertise [name] # Start advertising
> browse # Scan for devices
> connect <number> # Connect to discovered device
> pin <code> # Enter PIN for authentication
> send <message> # Send encrypted message
> trust # Trust current device
> disconnect # Close connection
> status # Show connection info
> quit # ExitExample session:
$ swift run WiPeerKitCLI
WiPeerKit CLI v2.0
==================
> advertise "My Mac"
📢 Advertising as 'My Mac'
🔑 Connection PIN: 425-817
> status
Service: Advertising ✓
Connected: No
Security: PIN Required┌─────────────────────────────────────┐
│ WiPeerKit (Main API) │
├─────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Service │ │ Secure │ │
│ │ Discovery │ │ Connection │ │
│ └─────────────┘ └──────────────┘ │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ TCP │ │ Encryption │ │
│ │ Transport │ │ (AES-GCM) │ │
│ └─────────────┘ └──────────────┘ │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Message │ │ Key │ │
│ │ Protocol │ │ Exchange │ │
│ └─────────────┘ └──────────────┘ │
└─────────────────────────────────────┘
All components are implemented as actors for thread safety:
actor ServiceDiscoveryActor // Manages mDNS operations
actor TCPTransportActor // Handles socket communication
actor EncryptionActor // Cryptographic operations
actor SecureConnectionManager // Authentication & authorization- Discovery: mDNS advertises/browses for services
- Connection: TCP socket established between peers
- Authentication: Identity verified through chosen method
- Key Exchange: ECDH generates session keys
- Communication: Messages encrypted with AES-GCM
Main entry point for the framework.
@MainActor
public final class WiPeerKit {
// Connection state
@Published public private(set) var connectionState: ConnectionState
@Published public private(set) var discoveredPeers: [DiscoveredPeer]
// Callbacks
public var onDataReceived: (@Sendable (Data) -> Void)?
// Discovery
public func startAdvertising(serviceName: String? = nil)
public func stopAdvertising()
public func startBrowsing()
public func stopBrowsing()
// Connection
public func connect(to peer: DiscoveredPeer) async throws
public func secureConnect(to peer: DiscoveredPeer, options: ConnectionOptions) async throws
public func secureConnectWithAuth(to peer: DiscoveredPeer, using: SecureConnectionManager) async throws
public func disconnect()
// Communication
public func send(data: Data) async throws
public func send(message: String) async throws
}Handles authentication and authorization.
actor SecureConnectionManager {
// Authentication methods
enum AuthenticationMethod {
case pin(String)
case qrCode(Data)
case manualApproval
case trustedDevices([DeviceIdentity])
}
// Callbacks
var onConnectionRequest: (@Sendable (DeviceIdentity) async -> Bool)?
var onPinRequested: (@Sendable () async -> String?)?
// Trust management
func addTrustedDevice(_ device: DeviceIdentity) async
func removeTrustedDevice(_ device: DeviceIdentity) async
func getTrustedDevices() async -> [DeviceIdentity]
}// Discovered peer information
public struct DiscoveredPeer: Sendable {
public let id: String
public let name: String
public let host: String?
public let port: Int?
}
// Connection states
public enum ConnectionState: Sendable {
case disconnected
case connecting
case connected
case failed(Error)
}
// Device identity
public struct DeviceIdentity: Sendable {
public let id: UUID
public let name: String
public let fingerprint: String
public let modelIdentifier: String
}# Run all tests
swift test
# Run specific test
swift test --filter SecurityTests
# Run with coverage
swift test --enable-code-coverage- Unit Tests: Test individual components in isolation
- Integration Tests: Test component interactions
- Security Tests: Verify encryption and authentication
- Performance Tests: Measure throughput and latency
@MainActor
final class MyWiPeerKitTests: XCTestCase {
func testSecureConnection() async throws {
// Given
let alice = WiPeerKit()
let bob = WiPeerKit()
let aliceManager = try SecureConnectionManager(authMethod: .pin("123456"))
let bobManager = try SecureConnectionManager(authMethod: .pin("123456"))
// When
alice.startAdvertising()
bob.startBrowsing()
// Allow discovery
try await Task.sleep(nanoseconds: 1_000_000_000)
// Then
XCTAssertFalse(bob.discoveredPeers.isEmpty)
// Connect securely
let peer = bob.discoveredPeers.first!
try await bob.secureConnectWithAuth(to: peer, using: bobManager)
XCTAssertEqual(bob.connectionState, .connected)
}
}- Ensure NSLocalNetworkUsageDescription is in Info.plist
- User must grant permission in Settings
- Verify both devices are on same Wi-Fi network
- Check firewall settings
- Ensure service type matches
- Verify authentication credentials (PIN, etc.)
- Check for timeout issues
- Enable debug logging
// Enable verbose logging
WiPeerKit.enableDebugLogging = true
// Custom log handler
WiPeerKit.logHandler = { level, message in
print("[\(level)] \(message)")
}// Get connection statistics
let stats = await peerKit.getConnectionStats()
print("Bytes sent: \(stats.bytesSent)")
print("Bytes received: \(stats.bytesReceived)")
print("Round trip time: \(stats.roundTripTime)ms")We welcome contributions!
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Submit a pull request
- Follow Swift API Design Guidelines
- Use SwiftLint configuration
- Document public APIs
- Add unit tests for new features
WiPeerKit is available under the MIT license. See LICENSE for details.
- Apple's Network framework team
- CryptoKit contributors
- Swift community for concurrency feedback
For questions and support, please open an issue on GitHub.