A modern Swift implementation of the Homer Encapsulation Protocol (HEP) capture agent for duplicating SIP messages and other network protocols to Homer/SIPCAPTURE monitoring systems.
- β HEP v2 and v3 Protocol Support
- β IPv4 and IPv6 Support
- β SSL/TLS Encryption
- β Payload Compression (zlib)
- β Authentication Support
- β Thread-Safe Operations
- β Modern Swift Network Framework
- β Memory Safe (No Manual Memory Management)
- β Type Safe with Swift's Strong Typing
- iOS 12.0+ / macOS 10.14+ / tvOS 12.0+ / watchOS 5.0+
- Swift 5.0+
- Xcode 10.0+
Add the following to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/yourusername/hep-swift.git", from: "1.0.0")
]
- Download the
HEPCaptureAgent.swift
file - Add it to your Xcode project
- Import the necessary frameworks in your project:
Network
Compression
CryptoKit
import Foundation
// Create HEP capture agent
let agent = HEPCaptureAgent(
host: "10.0.0.1", // Homer server IP
port: "9060", // Homer server port
captureId: 101, // Capture node ID
version: 3, // HEP version (2 or 3)
useSSL: false, // Enable SSL/TLS
compress: false, // Enable payload compression
password: nil // Authentication password
)
// Create connection info
let rcInfo = RemoteConnectionInfo(
ipFamily: 2, // AF_INET (IPv4)
ipProto: 17, // IPPROTO_UDP
srcIP: "192.168.1.100",
dstIP: "192.168.1.200",
srcPort: 5060,
dstPort: 5060,
timeSec: UInt32(Date().timeIntervalSince1970),
timeUsec: 0,
protoType: 1 // Protocol type (1 = SIP)
)
// Send SIP message
let sipMessage = """
INVITE sip:[email protected] SIP/2.0
Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK-123456
From: Bob <sip:[email protected]>;tag=abc123
To: Alice <sip:[email protected]>
Call-ID: [email protected]
CSeq: 1 INVITE
Contact: <sip:[email protected]:5060>
Content-Length: 0
""".data(using: .utf8)!
let success = agent.sendHEPBasic(rcInfo: rcInfo, data: sipMessage)
print("Message sent successfully: \(success)")
func captureSIPMessage() {
let agent = HEPCaptureAgent(host: "homer.example.com", port: "9060")
let connectionInfo = RemoteConnectionInfo(
ipFamily: 2, // IPv4
ipProto: 17, // UDP
srcIP: "10.0.1.100",
dstIP: "10.0.1.200",
srcPort: 5060,
dstPort: 5060,
timeSec: UInt32(Date().timeIntervalSince1970),
timeUsec: UInt32(Date().timeIntervalSince1970.truncatingRemainder(dividingBy: 1) * 1_000_000),
protoType: 1 // SIP
)
let sipInvite = """
INVITE sip:[email protected] SIP/2.0
Via: SIP/2.0/UDP 10.0.1.100:5060
From: "Caller" <sip:[email protected]>;tag=12345
To: "Callee" <sip:[email protected]>
Call-ID: [email protected]
CSeq: 1 INVITE
Content-Type: application/sdp
Content-Length: 142
v=0
o=- 12345 67890 IN IP4 10.0.1.100
c=IN IP4 10.0.1.100
m=audio 8000 RTP/AVP 0
a=rtpmap:0 PCMU/8000
""".data(using: .utf8)!
if agent.sendHEPBasic(rcInfo: connectionInfo, data: sipInvite) {
print("β
SIP INVITE captured successfully")
} else {
print("β Failed to capture SIP INVITE")
}
}
func captureWithAdvancedFeatures() {
let agent = HEPCaptureAgent(
host: "2001:db8::1", // IPv6 Homer server
port: "9061", // SSL port
captureId: 201,
version: 3,
useSSL: true, // Enable SSL
compress: true, // Enable compression
password: "secret123" // Authentication
)
let connectionInfo = RemoteConnectionInfo(
ipFamily: 30, // AF_INET6
ipProto: 6, // TCP
srcIP: "2001:db8::100",
dstIP: "2001:db8::200",
srcPort: 5060,
dstPort: 5060,
timeSec: UInt32(Date().timeIntervalSince1970),
timeUsec: 0,
protoType: 1
)
let largePayload = String(repeating: "SIP message data ", count: 100).data(using: .utf8)!
let result = agent.sendHEPBasic(rcInfo: connectionInfo, data: largePayload)
print("Compressed IPv6 SSL capture: \(result ? "Success" : "Failed")")
// Print statistics
print(agent.getStatistics())
}
import Network
class SIPMonitor {
private let hepAgent: HEPCaptureAgent
private var udpListener: NWListener?
init(homerHost: String, homerPort: String) {
self.hepAgent = HEPCaptureAgent(
host: homerHost,
port: homerPort,
captureId: 100,
version: 3
)
setupUDPListener()
}
private func setupUDPListener() {
let parameters = NWParameters.udp
parameters.allowLocalEndpointReuse = true
do {
udpListener = try NWListener(using: parameters, on: 5060)
udpListener?.newConnectionHandler = { [weak self] connection in
self?.handleNewConnection(connection)
}
udpListener?.start(queue: DispatchQueue.global())
print("π§ SIP UDP listener started on port 5060")
} catch {
print("β Failed to start UDP listener: \(error)")
}
}
private func handleNewConnection(_ connection: NWConnection) {
connection.start(queue: DispatchQueue.global())
func receiveData() {
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] data, context, isComplete, error in
if let data = data, !data.isEmpty {
self?.processSIPPacket(data: data, connection: connection)
}
if !isComplete {
receiveData() // Continue receiving
}
}
}
receiveData()
}
private func processSIPPacket(data: Data, connection: NWConnection) {
guard let remoteEndpoint = connection.endpoint,
case .hostPort(let host, let port) = remoteEndpoint else {
return
}
let connectionInfo = RemoteConnectionInfo(
ipFamily: host.debugDescription.contains(":") ? 30 : 2, // IPv6 vs IPv4
ipProto: 17, // UDP
srcIP: "0.0.0.0", // Would need to extract from actual packet
dstIP: host.debugDescription,
srcPort: 5060,
dstPort: UInt16(port.rawValue),
timeSec: UInt32(Date().timeIntervalSince1970),
timeUsec: 0,
protoType: 1
)
let success = hepAgent.sendHEPBasic(rcInfo: connectionInfo, data: data)
if success {
print("π‘ Captured SIP packet (\(data.count) bytes)")
}
}
func stop() {
udpListener?.cancel()
hepAgent.disconnect()
print("π SIP monitor stopped")
}
}
// Usage
let monitor = SIPMonitor(homerHost: "homer.company.com", port: "9060")
// Monitor will automatically capture and forward SIP packets
// Stop monitoring when done
// monitor.stop()
func batchCapture() {
let agent = HEPCaptureAgent(host: "10.0.0.100", port: "9060")
let messages = [
("INVITE", "INVITE sip:[email protected] SIP/2.0\r\n..."),
("200 OK", "SIP/2.0 200 OK\r\n..."),
("ACK", "ACK sip:[email protected] SIP/2.0\r\n..."),
("BYE", "BYE sip:[email protected] SIP/2.0\r\n...")
]
var successCount = 0
var failureCount = 0
for (messageType, content) in messages {
let connectionInfo = RemoteConnectionInfo(
ipFamily: 2,
ipProto: 17,
srcIP: "192.168.1.10",
dstIP: "192.168.1.20",
srcPort: 5060,
dstPort: 5060,
timeSec: UInt32(Date().timeIntervalSince1970),
timeUsec: 0,
protoType: 1
)
guard let data = content.data(using: .utf8) else {
print("β Failed to encode \(messageType)")
failureCount += 1
continue
}
if agent.sendHEPBasic(rcInfo: connectionInfo, data: data) {
print("β
\(messageType) captured successfully")
successCount += 1
} else {
print("β Failed to capture \(messageType)")
failureCount += 1
}
// Small delay between messages
Thread.sleep(forTimeInterval: 0.1)
}
print("\nπ Batch Results:")
print(" Successful: \(successCount)")
print(" Failed: \(failureCount)")
print(" \(agent.getStatistics())")
}
Parameter | Type | Default | Description |
---|---|---|---|
host |
String | "10.0.0.1" | Homer server hostname or IP address |
port |
String | "9060" | Homer server port |
captureId |
UInt32 | 101 | Unique capture node identifier |
version |
Int | 3 | HEP protocol version (2 or 3) |
useSSL |
Bool | false | Enable SSL/TLS encryption |
compress |
Bool | false | Enable payload compression |
password |
String? | nil | Authentication password |
Parameter | Type | Description |
---|---|---|
ipFamily |
UInt8 | 2 for IPv4, 30 for IPv6 |
ipProto |
UInt8 | 6 for TCP, 17 for UDP |
srcIP |
String | Source IP address |
dstIP |
String | Destination IP address |
srcPort |
UInt16 | Source port number |
dstPort |
UInt16 | Destination port number |
timeSec |
UInt32 | Timestamp seconds since epoch |
timeUsec |
UInt32 | Timestamp microseconds |
protoType |
UInt8 | Protocol type (1=SIP, 5=RTCP, etc.) |
Value | Protocol |
---|---|
1 | SIP |
5 | RTCP |
8 | H323 |
9 | SDP |
10 | RTP |
34 | Megaco/H248 |
35 | M2UA |
36 | M3UA |
37 | IAX |
38 | H322 |
39 | Skinny |
The library provides several ways to handle errors:
let agent = HEPCaptureAgent(host: "invalid-host", port: "9060")
// Check connection before sending
if agent.sendHEPBasic(rcInfo: connectionInfo, data: data) {
print("Success!")
} else {
print("Failed to send - check network connectivity")
print(agent.getStatistics()) // Check for connection issues
}
// Monitor statistics for failures
let stats = agent.getStatistics()
print(stats) // Shows packet counts and any failures
- Reuse Agent Instances: Create one agent per Homer server and reuse it
- Batch Operations: Send multiple packets without recreating connections
- Use Compression: Enable compression for large payloads to reduce bandwidth
- Monitor Statistics: Check packet counts to ensure delivery
- Connection Pooling: For high-volume scenarios, consider multiple agent instances
The HEP Swift library is thread-safe and can be used from multiple threads simultaneously:
let agent = HEPCaptureAgent(host: "homer.example.com", port: "9060")
// Safe to call from multiple threads
DispatchQueue.global().async {
agent.sendHEPBasic(rcInfo: connectionInfo1, data: data1)
}
DispatchQueue.global().async {
agent.sendHEPBasic(rcInfo: connectionInfo2, data: data2)
}
This project is licensed under the MIT License
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
For issues and questions:
- Open an issue on GitHub
- Check the Homer Project documentation
- Review the original captagent C implementation
- Initial Swift implementation
- HEP v2 and v3 support
- IPv4/IPv6 support
- SSL/TLS encryption
- Payload compression
- Thread-safe operations