Untitled

 avatar
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