ArpTableParserTests.cs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. using RackPeek.Domain.Discovery;
  2. namespace Tests.Discovery;
  3. /// <summary>
  4. /// The ARP table is where a scanned host's identity comes from, and the two
  5. /// platforms print it differently — most dangerously, macOS drops leading zeros
  6. /// from MAC octets. If normalisation slips, the same machine gets a different
  7. /// discovery id depending on which workstation ran the scan.
  8. /// </summary>
  9. public class ArpTableParserTests {
  10. [Fact]
  11. public void The_linux_proc_file_parses_to_normalised_macs() {
  12. IReadOnlyDictionary<string, string> table = ArpTableParser.Parse(Fixture.Read("linux-arp-table"));
  13. Assert.Equal("a4:91:b1:4e:3c:20", table["192.168.1.1"]);
  14. // Uppercase in the fixture, stored lowercase.
  15. Assert.Equal("dc:a6:32:0f:11:22", table["192.168.1.20"]);
  16. }
  17. [Fact]
  18. public void The_macos_arp_output_parses_to_the_same_macs_as_linux() {
  19. IReadOnlyDictionary<string, string> linux = ArpTableParser.Parse(Fixture.Read("linux-arp-table"));
  20. IReadOnlyDictionary<string, string> macos = ArpTableParser.Parse(Fixture.Read("macos-arp-output"));
  21. // The macOS fixture prints 192.168.1.20 as dc:a6:32:f:11:22 — unpadded. Identity
  22. // must not depend on which of the two formats happened to report the machine.
  23. Assert.Equal(linux["192.168.1.1"], macos["192.168.1.1"]);
  24. Assert.Equal(linux["192.168.1.20"], macos["192.168.1.20"]);
  25. }
  26. [Fact]
  27. public void The_windows_arp_output_parses_to_the_same_macs_as_linux() {
  28. IReadOnlyDictionary<string, string> linux = ArpTableParser.Parse(Fixture.Read("linux-arp-table"));
  29. IReadOnlyDictionary<string, string> windows = ArpTableParser.Parse(Fixture.Read("windows-arp-output"));
  30. // Windows prints dashes and uppercase; the interface/header lines parse to nothing.
  31. Assert.Equal(linux["192.168.1.1"], windows["192.168.1.1"]);
  32. Assert.Equal(linux["192.168.1.20"], windows["192.168.1.20"]);
  33. Assert.False(windows.ContainsKey("Interface:"));
  34. }
  35. [Fact]
  36. public void Unresolved_neighbours_contribute_nothing() {
  37. IReadOnlyDictionary<string, string> linux = ArpTableParser.Parse(Fixture.Read("linux-arp-table"));
  38. IReadOnlyDictionary<string, string> macos = ArpTableParser.Parse(Fixture.Read("macos-arp-output"));
  39. // Linux marks failures with flags 0x0 or an all-zero MAC; macOS prints "(incomplete)".
  40. Assert.False(linux.ContainsKey("192.168.1.50"));
  41. Assert.False(linux.ContainsKey("192.168.1.60"));
  42. Assert.False(macos.ContainsKey("192.168.1.50"));
  43. }
  44. [Theory]
  45. [InlineData(null)]
  46. [InlineData("")]
  47. [InlineData("not an arp table at all")]
  48. [InlineData("IP address HW type Flags HW address Mask Device")]
  49. [InlineData("? (garbage at nothing")]
  50. public void Garbage_input_is_an_empty_table_not_an_exception(string? text) =>
  51. Assert.Empty(ArpTableParser.Parse(text));
  52. [Theory]
  53. [InlineData("A4:91:B1:4E:3C:20", "a4:91:b1:4e:3c:20")]
  54. [InlineData("1:0:5e:0:0:fb", "01:00:5e:00:00:fb")]
  55. [InlineData("dc:a6:32:f:11:22", "dc:a6:32:0f:11:22")]
  56. [InlineData("A4-91-B1-4E-3C-20", "a4:91:b1:4e:3c:20")] // Windows separators
  57. public void Macs_normalise_to_lowercase_padded_octets(string raw, string expected) =>
  58. Assert.Equal(expected, ArpTableParser.NormaliseMac(raw));
  59. [Theory]
  60. [InlineData(null)]
  61. [InlineData("")]
  62. [InlineData("00:00:00:00:00:00")] // the kernel's "never answered"
  63. [InlineData("a4:91:b1:4e:3c")] // five octets
  64. [InlineData("a4:91:b1:4e:3c:20:ff")] // seven octets
  65. [InlineData("zz:91:b1:4e:3c:20")] // not hex
  66. [InlineData("(incomplete)")]
  67. public void Anything_that_is_not_a_usable_mac_is_null(string? raw) =>
  68. Assert.Null(ArpTableParser.NormaliseMac(raw));
  69. }