Untitled
unknown
plain_text
a year ago
4.6 kB
10
Indexable
import Foundation
import Network
class WakeOnLanService {
private let numbersOfBytesInMac = 6
private let syncStreamSize = 6
private let macDuplicationCount = 16
func wakeAndWaitDeviceOnline(mac: String?, ip: String, port: UInt16 = 9, timeout: TimeInterval = 10.0) async -> Bool {
wakeOnLan(mac: mac, ip: ip, port: port)
return await pingUntilOnline(ip: ip, timeout: timeout)
}
func wakeOnLan(mac: String?, ip: String, port: UInt16 = 9) {
guard let mac = mac else { return }
print("WakeOnLan: Entry $mac), $ip), $port)")
do {
let socket = try Socket(address: ip, port: port)
let packet = getMagicPacket(mac: mac, ip: ip, port: port)
try socket.send(data: packet)
socket.close()
print("WakeOnLan: Done")
} catch {
print("WakeOnLan: Failed to send Wake-on-LAN packet: $error)")
}
}
func pingUntilOnline(ip: String, timeout: TimeInterval = 7.0) async -> Bool {
let endDate = Date().addingTimeInterval(timeout)
while Date() < endDate {
if ping(ip: ip) {
print("WakeOnLan: pingUntilOnline success")
return true
}
try? await Task.sleep(nanoseconds: 500_000_000)
}
print("WakeOnLan: pingUntilOnline Timeout")
return false
}
func ping(ip: String) -> Bool {
let host = NWEndpoint.Host(ip)
let params = NWParameters.tcp
let connection = NWConnection(host: host, port: 80, using: params)
let semaphore = DispatchSemaphore(value: 0)
var isReachable = false
connection.stateUpdateHandler = { state in
switch state {
case .ready:
print("WakeOnLan - Ping: $ip) is reachable")
isReachable = true
semaphore.signal()
case .failed(let error):
print("WakeOnLan - Ping: $ip) is not reachable, error: $error)")
isReachable = false
semaphore.signal()
default:
break
}
}
connection.start(queue: .global())
semaphore.wait()
connection.cancel()
return isReachable
}
private func getMagicPacket(mac: String, ip: String, port: UInt16) -> Data {
return getMagicPacketBytes(mac: mac)
}
private func getMagicPacketBytes(mac: String) -> Data {
var macBytes = getMacBytes(mac: mac)
var bytes = Data(count: syncStreamSize + macDuplicationCount * macBytes.count)
// The Synchronization Stream is defined as 6 bytes of FFh
bytes.replaceSubrange(0..<syncStreamSize, with: UInt8)
// The Target MAC block contains 16 duplications of the IEEE address of the target, with no breaks or interruptions
for i in stride(from: syncStreamSize, to: bytes.count, by: macBytes.count) {
bytes.replaceSubrange(i..<i + macBytes.count, with: macBytes)
}
return bytes
}
private func getMacBytes(mac: String) -> [UInt8] {
let hex = mac.split(separator: ":")
if hex.count != numbersOfBytesInMac {
fatalError("Invalid MAC address")
}
return hex.map { UInt8($0, radix: 16)! }
}
}
class Socket {
let address: String
let port: UInt16
var socket: Int32
init(address: String, port: UInt16) throws {
self.address = address
self.port = port
self.socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
guard self.socket > 0 else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
}
func send(data: Data) throws {
var addr = sockaddr_in()
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = htons(port)
addr.sin_addr.s_addr = inet_addr(address)
let sent = data.withUnsafeBytes {
sendto(socket, $0.baseAddress, data.count, 0, sockaddr_cast(&addr), socklen_t(MemoryLayout<sockaddr_in>.size))
}
guard sent > 0 else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
}
func close() {
Darwin.close(socket)
}
private func sockaddr_cast(_ p: UnsafeMutablePointer<sockaddr_in>) -> UnsafePointer<sockaddr> {
return UnsafeRawPointer(p).assumingMemoryBound(to: sockaddr.self)
}
}Editor is loading...
Leave a Comment