package wol import ( "net" "syscall" "testing" "time" ) func TestParseMAC(t *testing.T) { tests := []struct { input string wantOK bool wantBytes [6]byte }{ {"AA:BB:CC:DD:EE:FF", true, [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}}, {"aa:bb:cc:dd:ee:ff", true, [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}}, {"AA-BB-CC-DD-EE-FF", true, [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}}, {"11:22:33:44:55:66", true, [6]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}}, {"not:a:mac:addre", false, [6]byte{}}, {"GG:HH:II:JJ:KK:LL", false, [6]byte{}}, {"aa:bb:cc:dd", false, [6]byte{}}, {"", false, [6]byte{}}, } for _, tt := range tests { mac, err := ParseMAC(tt.input) if tt.wantOK { if err != nil { t.Errorf("ParseMAC(%q) unexpected error: %v", tt.input, err) continue } if mac != tt.wantBytes { t.Errorf("ParseMAC(%q) = %v, want %v", tt.input, mac, tt.wantBytes) } } else { if err == nil { t.Errorf("ParseMAC(%q) expected error, got nil", tt.input) } } } } func TestFormatMAC(t *testing.T) { mac := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF} got := FormatMAC(mac) want := "aa:bb:cc:dd:ee:ff" if got != want { t.Errorf("FormatMAC() = %q, want %q", got, want) } } func TestBuildMagicPacket(t *testing.T) { mac := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF} packet := BuildMagicPacket(mac) if len(packet) != 102 { t.Errorf("BuildMagicPacket length = %d, want 102", len(packet)) } for i := 0; i < 6; i++ { if packet[i] != 0xFF { t.Errorf("packet[%d] = %02x, want FF", i, packet[i]) } } for i := 0; i < 16; i++ { offset := 6 + i*6 for j := 0; j < 6; j++ { if packet[offset+j] != mac[j] { t.Errorf("packet[%d] = %02x, want %02x (rep %d)", offset+j, packet[offset+j], mac[j], i) } } } } func TestSendInvalidBroadcast(t *testing.T) { mac, _ := ParseMAC("aa:bb:cc:dd:ee:ff") err := Send(mac, "not-an-ip") if err == nil { t.Error("Send with invalid broadcast address expected error, got nil") } } func TestSendThreePacketsToListener(t *testing.T) { mac, _ := ParseMAC("aa:bb:cc:dd:ee:ff") ln, err := net.ListenPacket("udp", "127.0.0.1:9") if err != nil { t.Skipf("skipping test (need port 9, likely requires root): %v", err) } defer ln.Close() err = Send(mac, "127.0.0.1") if err != nil { t.Errorf("Send to 127.0.0.1: expected nil, got %v", err) } } func TestSendPacketReceived(t *testing.T) { mac, _ := ParseMAC("aa:bb:cc:dd:ee:ff") ln, err := net.ListenPacket("udp", "127.0.0.1:0") if err != nil { t.Skipf("skipping receive test: %v", err) } defer ln.Close() addr := ln.LocalAddr().(*net.UDPAddr) deadline := time.Now().Add(5 * time.Second) errCh := make(chan error, 1) go func() { time.Sleep(500 * time.Millisecond) conn, err := net.DialUDP("udp4", nil, &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: addr.Port}) if err != nil { errCh <- err return } sc, err := conn.SyscallConn() if err != nil { conn.Close() errCh <- err return } if err := sc.Control(func(fd uintptr) { err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1) }); err != nil { conn.Close() errCh <- err return } conn.SetWriteDeadline(time.Now().Add(3 * time.Second)) packet := BuildMagicPacket(mac) for i := 0; i < 3; i++ { conn.Write(packet) time.Sleep(100 * time.Millisecond) } conn.Close() errCh <- nil }() expected := BuildMagicPacket(mac) buf := make([]byte, 102) rcvd := 0 for rcvd < 3 { ln.SetReadDeadline(deadline) n, _, err := ln.ReadFrom(buf) if err != nil { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { break } t.Fatalf("unexpected error reading packet %d: %v", rcvd+1, err) } if n != len(expected) { t.Errorf("packet %d: got length %d, want %d", rcvd+1, n, len(expected)) continue } if string(buf[:n]) != string(expected[:n]) { t.Errorf("packet %d content mismatch", rcvd+1) } rcvd++ } if rcvd != 3 { t.Errorf("expected 3 packets, received %d", rcvd) } select { case sendErr := <-errCh: if sendErr != nil { t.Logf("sender goroutine error (ignored): %v", sendErr) } default: } }