Matthias Nott
2026-04-05 0cfa6e45729b4546ad3d762333230fe29f5151b2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import 'dart:io';
import 'dart:typed_data';
/// Wake-on-LAN magic packet sender.
class WolService {
  WolService._();
  /// Parse a MAC address string (colon or dash separated) to bytes.
  static List<int>? _parseMac(String mac) {
    final cleaned = mac.replaceAll(RegExp(r'[:\-]'), '');
    if (cleaned.length != 12) return null;
    final bytes = <int>[];
    for (var i = 0; i < 12; i += 2) {
      final byte = int.tryParse(cleaned.substring(i, i + 2), radix: 16);
      if (byte == null) return null;
      bytes.add(byte);
    }
    return bytes;
  }
  /// Build the magic packet: 6x 0xFF followed by MAC address repeated 16 times.
  static Uint8List _buildPacket(List<int> macBytes) {
    final packet = BytesBuilder();
    // 6 bytes of 0xFF
    for (var i = 0; i < 6; i++) {
      packet.addByte(0xFF);
    }
    // MAC address repeated 16 times
    for (var i = 0; i < 16; i++) {
      packet.add(macBytes);
    }
    return packet.toBytes();
  }
  /// Derive subnet broadcast from an IP address (e.g., 192.168.1.100 → 192.168.1.255).
  static String? _subnetBroadcast(String? ip) {
    if (ip == null || ip.isEmpty) return null;
    final parts = ip.split('.');
    if (parts.length != 4) return null;
    return '${parts[0]}.${parts[1]}.${parts[2]}.255';
  }
  /// Send a Wake-on-LAN packet for the given MAC address.
  /// Broadcasts to 255.255.255.255 and subnet broadcast derived from localHost.
  /// Sends on ports 7 and 9 for maximum compatibility.
  static Future<void> wake(String macAddress, {String? localHost}) async {
    final macBytes = _parseMac(macAddress);
    if (macBytes == null) {
      throw ArgumentError('Invalid MAC address: $macAddress');
    }
    final packet = _buildPacket(macBytes);
    final socket = await RawDatagramSocket.bind(
      InternetAddress.anyIPv4,
      0,
    );
    socket.broadcastEnabled = true;
    final targets = <InternetAddress>[
      InternetAddress('255.255.255.255'),
    ];
    // Add subnet broadcast derived from localHost
    final subnet = _subnetBroadcast(localHost);
    if (subnet != null) {
      try {
        targets.add(InternetAddress(subnet));
      } catch (_) {}
    }
    // Send to all targets on both common WoL ports
    for (final addr in targets) {
      socket.send(packet, addr, 9);
      socket.send(packet, addr, 7);
    }
    // Repeat for reliability
    for (var i = 0; i < 3; i++) {
      await Future.delayed(const Duration(milliseconds: 100));
      for (final addr in targets) {
        socket.send(packet, addr, 9);
      }
    }
    socket.close();
  }
}