system_utils.hpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. * Copyright (C) 2014 Pavel Kirienko <pavel.kirienko@gmail.com>
  3. */
  4. #pragma once
  5. #include <array>
  6. #include <cassert>
  7. #include <cctype>
  8. #include <fstream>
  9. #include <string>
  10. #include <vector>
  11. #include <cstdint>
  12. #include <algorithm>
  13. #include <utility>
  14. #include <uavcan_linux/exception.hpp>
  15. #include <uavcan/data_type.hpp>
  16. namespace uavcan_linux
  17. {
  18. /**
  19. * This class can find and read machine ID from a text file, represented as 32-char (16-byte) long hexadecimal string,
  20. * possibly with separators (like dashes or colons). If the available ID is more than 16 bytes, extra bytes will be
  21. * ignored. A shorter ID will not be accepted as valid.
  22. * In order to be read, the ID must be located on the first line of the file and must not contain any whitespace
  23. * characters.
  24. *
  25. * Examples of valid ID:
  26. * 0123456789abcdef0123456789abcdef
  27. * 20CE0b1E-8C03-07C8-13EC-00242C491652
  28. */
  29. class MachineIDReader
  30. {
  31. public:
  32. static constexpr int MachineIDSize = 16;
  33. typedef std::array<std::uint8_t, MachineIDSize> MachineID;
  34. static std::vector<std::string> getDefaultSearchLocations()
  35. {
  36. return
  37. {
  38. "/etc/machine-id",
  39. "/var/lib/dbus/machine-id",
  40. "/sys/class/dmi/id/product_uuid"
  41. };
  42. }
  43. private:
  44. const std::vector<std::string> search_locations_;
  45. static std::vector<std::string> mergeLists(const std::vector<std::string>& a, const std::vector<std::string>& b)
  46. {
  47. std::vector<std::string> ab;
  48. ab.reserve(a.size() + b.size());
  49. ab.insert(ab.end(), a.begin(), a.end());
  50. ab.insert(ab.end(), b.begin(), b.end());
  51. return ab;
  52. }
  53. bool tryRead(const std::string& location, MachineID& out_id) const
  54. {
  55. /*
  56. * Reading the file
  57. */
  58. std::string token;
  59. try
  60. {
  61. std::ifstream infile(location);
  62. infile >> token;
  63. }
  64. catch (std::exception&)
  65. {
  66. return false;
  67. }
  68. /*
  69. * Preprocessing the input string - convert to lowercase, remove all non-hex characters, limit to 32 chars
  70. */
  71. std::transform(token.begin(), token.end(), token.begin(), [](char x) { return std::tolower(x); });
  72. token.erase(std::remove_if(token.begin(), token.end(),
  73. [](char x){ return (x < 'a' || x > 'f') && !std::isdigit(x); }),
  74. token.end());
  75. if (token.length() < (MachineIDSize * 2))
  76. {
  77. return false;
  78. }
  79. token.resize(MachineIDSize * 2); // Truncating
  80. /*
  81. * Parsing the string as hex bytes
  82. */
  83. auto sym = std::begin(token);
  84. for (auto& byte : out_id)
  85. {
  86. assert(sym != std::end(token));
  87. byte = std::stoi(std::string{*sym++, *sym++}, nullptr, 16);
  88. }
  89. return true;
  90. }
  91. public:
  92. /**
  93. * This class can use extra seach locations. If provided, they will be checked first, before default ones.
  94. */
  95. MachineIDReader(const std::vector<std::string>& extra_search_locations = {})
  96. : search_locations_(mergeLists(extra_search_locations, getDefaultSearchLocations()))
  97. { }
  98. /**
  99. * Just like @ref readAndGetLocation(), but this one doesn't return location where this ID was obtained from.
  100. */
  101. MachineID read() const { return readAndGetLocation().first; }
  102. /**
  103. * This function checks available search locations and reads the ID from the first valid location.
  104. * It returns std::pair<> with ID and the file path where it was read from.
  105. * In case if none of the search locations turned out to be valid, @ref uavcan_linux::Exception will be thrown.
  106. */
  107. std::pair<MachineID, std::string> readAndGetLocation() const
  108. {
  109. for (auto x : search_locations_)
  110. {
  111. auto out = MachineID();
  112. if (tryRead(x, out))
  113. {
  114. return {out, x};
  115. }
  116. }
  117. throw Exception("Failed to read machine ID");
  118. }
  119. };
  120. /**
  121. * This class computes unique ID for a UAVCAN node in a Linux application.
  122. * It takes the following inputs:
  123. * - Unique machine ID
  124. * - Node name string (e.g. "org.uavcan.linux_app.dynamic_node_id_server")
  125. * - Instance ID byte, e.g. node ID (optional)
  126. */
  127. inline std::array<std::uint8_t, 16> makeApplicationID(const MachineIDReader::MachineID& machine_id,
  128. const std::string& node_name,
  129. const std::uint8_t instance_id = 0)
  130. {
  131. union HalfID
  132. {
  133. std::uint64_t num;
  134. std::uint8_t bytes[8];
  135. HalfID(std::uint64_t arg_num) : num(arg_num) { }
  136. };
  137. std::array<std::uint8_t, 16> out;
  138. // First 8 bytes of the application ID are CRC64 of the machine ID in native byte order
  139. {
  140. uavcan::DataTypeSignatureCRC crc;
  141. crc.add(machine_id.data(), static_cast<unsigned>(machine_id.size()));
  142. HalfID half(crc.get());
  143. std::copy_n(half.bytes, 8, out.begin());
  144. }
  145. // Last 8 bytes of the application ID are CRC64 of the node name and optionally node ID
  146. {
  147. uavcan::DataTypeSignatureCRC crc;
  148. crc.add(reinterpret_cast<const std::uint8_t*>(node_name.c_str()), static_cast<unsigned>(node_name.length()));
  149. crc.add(instance_id);
  150. HalfID half(crc.get());
  151. std::copy_n(half.bytes, 8, out.begin() + 8);
  152. }
  153. return out;
  154. }
  155. }