Untitled
unknown
plain_text
9 months ago
4.6 kB
5
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