Aktualizr
C++ SOTA Client
opcuabridgeclient.cc
1 #include "opcuabridgeclient.h"
2 #include "opcuabridgediscoveryclient.h"
3 
4 #include "logging/logging.h"
5 
6 #include "uptane/secondaryconfig.h"
7 
8 #include <open62541.h>
9 
10 #include <cstdlib>
11 #include <ctime>
12 #include <random>
13 #include <unordered_set>
14 
15 #include <boost/filesystem.hpp>
16 #include <boost/functional/hash.hpp>
17 
18 namespace fs = boost::filesystem;
19 
20 namespace opcuabridge {
21 
22 SelectEndPoint::DiscoveredEndPointCacheEntryHash::result_type SelectEndPoint::DiscoveredEndPointCacheEntryHash::
23 operator()(SelectEndPoint::DiscoveredEndPointCacheEntryHash::argument_type const& e) const {
24  result_type seed = 0;
25  boost::hash_combine(seed, std::hash<Uptane::EcuSerial>()(e.serial));
26  boost::hash_combine(seed, std::hash<Uptane::HardwareIdentifier>()(e.hwid));
27  return seed;
28 }
29 
30 bool SelectEndPoint::DiscoveredEndPointCacheEntryEqual::operator()(
31  const SelectEndPoint::DiscoveredEndPointCacheEntryEqual::argument_type& lhs,
32  const SelectEndPoint::DiscoveredEndPointCacheEntryEqual::argument_type& rhs) const {
33  return (lhs.serial == rhs.serial && lhs.hwid == rhs.hwid);
34 }
35 
36 thread_local SelectEndPoint::DiscoveredEndPointCache SelectEndPoint::discovered_end_points_cache_;
37 
38 SelectEndPoint::SelectEndPoint(const Uptane::SecondaryConfig& sconfig) {
39  if (discovered_end_points_cache_.empty()) {
40  if (!sconfig.opcua_lds_url.empty()) {
41  considerLdsRegisteredEndPoints(sconfig.opcua_lds_url);
42  } else {
43  discovery::Client discovery_client(OPCUA_DISCOVERY_SERVICE_PORT);
44  for (const discovery::Client::discovered_endpoint& ep : discovery_client.getDiscoveredEndPoints()) {
45  if (ep.type == discovery::kLDS) {
46  considerLdsRegisteredEndPoints(makeOpcuaServerUri(ep.address));
47  } else if (ep.type == discovery::kNoLDS) {
48  std::string opcua_server_url = makeOpcuaServerUri(ep.address);
49  if (endPointConfirmed(opcua_server_url, sconfig)) {
50  discovered_end_points_cache_.insert(DiscoveredEndPointCacheEntry{
51  Uptane::EcuSerial(sconfig.ecu_serial), Uptane::HardwareIdentifier(sconfig.ecu_hardware_id), opcua_server_url});
52  }
53  } else {
54  LOG_INFO << "OPC-UA discover secondary: unsupported response from " << ep.address;
55  }
56  }
57  }
58  }
59  auto ep_it = discovered_end_points_cache_.find(DiscoveredEndPointCacheEntry{
60  Uptane::EcuSerial(sconfig.ecu_serial), Uptane::HardwareIdentifier(sconfig.ecu_hardware_id), std::string()});
61  if (ep_it != discovered_end_points_cache_.end()) {
62  url_ = ep_it->opcua_server_url;
63  }
64 }
65 
66 bool SelectEndPoint::endPointConfirmed(const std::string& opcua_server_url,
67  const Uptane::SecondaryConfig& sconfig) const {
68  auto probe_opcua_server_config = Client(opcua_server_url).recvConfiguration();
69  return (probe_opcua_server_config.getSerial() == Uptane::EcuSerial(sconfig.ecu_serial) &&
70  probe_opcua_server_config.getHwId() == Uptane::HardwareIdentifier(sconfig.ecu_hardware_id));
71 }
72 
73 std::string SelectEndPoint::makeOpcuaServerUri(const std::string& address) const {
74  return "opc.tcp://[" + address + "]:" + std::to_string(OPCUABRIDGE_PORT);
75 }
76 
77 void SelectEndPoint::considerLdsRegisteredEndPoints(const std::string& opcua_lds_url) {
78  UA_ApplicationDescription* applicationDescriptionArray = nullptr;
79  size_t applicationDescriptionArraySize = 0;
80 
81  UA_StatusCode retval;
82  {
83  UA_ClientConfig config = UA_ClientConfig_default;
84  config.timeout = OPCUABRIDGE_CLIENT_SYNC_RESPONSE_TIMEOUT_MS;
85  config.logger = &opcuabridge::BoostLogOpcua;
86 
87  UA_Client* client = UA_Client_new(config);
88  retval = UA_Client_findServers(client, opcua_lds_url.c_str(), 0, nullptr, 0, nullptr,
89  &applicationDescriptionArraySize, &applicationDescriptionArray);
90  UA_Client_delete(client);
91  }
92 
93  if (retval != UA_STATUSCODE_GOOD) {
94  LOG_ERROR << "OPC-UA LDS dicovery request: unable to get registered servers on " << opcua_lds_url;
95  return;
96  }
97 
98  for (size_t i = 0; i < applicationDescriptionArraySize; i++) {
99  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
100  UA_ApplicationDescription* description = &applicationDescriptionArray[i];
101  if (description->applicationType != UA_APPLICATIONTYPE_SERVER) {
102  continue;
103  }
104  if (description->discoveryUrlsSize == 0) {
105  LOG_INFO << "OPC-UA server " << std::string(reinterpret_cast<char*>(description->applicationUri.data),
106  description->applicationUri.length)
107  << " does not provide any discovery urls";
108  continue;
109  }
110 
111  UA_ClientConfig config = UA_ClientConfig_default;
112  config.timeout = OPCUABRIDGE_CLIENT_SYNC_RESPONSE_TIMEOUT_MS;
113  config.logger = &opcuabridge::BoostLogOpcua;
114  UA_Client* client = UA_Client_new(config);
115  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
116  std::string discovery_url(reinterpret_cast<char*>(description->discoveryUrls[0].data),
117  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
118  description->discoveryUrls[0].length);
119 
120  UA_EndpointDescription* endpointArray = nullptr;
121  size_t endpointArraySize = 0;
122  retval = UA_Client_getEndpoints(client, discovery_url.c_str(), &endpointArraySize, &endpointArray);
123 
124  if (retval != UA_STATUSCODE_GOOD) {
125  UA_Client_disconnect(client);
126  UA_Client_delete(client);
127  break;
128  }
129 
130  for (size_t j = 0; j < endpointArraySize; j++) {
131  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
132  UA_EndpointDescription* endpoint = &endpointArray[j];
133  std::string opcua_server_url =
134  std::string(reinterpret_cast<char*>(endpoint->endpointUrl.data), endpoint->endpointUrl.length);
135  auto opcua_server_config = Client(opcua_server_url).recvConfiguration();
136  discovered_end_points_cache_.insert(DiscoveredEndPointCacheEntry{
137  opcua_server_config.getSerial(), opcua_server_config.getHwId(), opcua_server_url});
138  }
139  UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
140  UA_Client_delete(client);
141  }
142  UA_Array_delete(applicationDescriptionArray, applicationDescriptionArraySize,
143  &UA_TYPES[UA_TYPES_APPLICATIONDESCRIPTION]);
144 }
145 
146 Client::Client(const SelectEndPoint& selector) noexcept : Client(selector.getUrl()) {}
147 
148 Client::Client(const std::string& url) noexcept {
149  UA_ClientConfig config = UA_ClientConfig_default;
150  config.timeout = OPCUABRIDGE_CLIENT_SYNC_RESPONSE_TIMEOUT_MS;
151  config.logger = &opcuabridge::BoostLogOpcua;
152  client_ = UA_Client_new(config);
153  UA_Client_connect(client_, url.c_str());
154 }
155 
156 Client::~Client() {
157  UA_Client_disconnect(client_);
158  UA_Client_delete(client_);
159 }
160 
161 Client::operator bool() const { return (UA_Client_getState(client_) != UA_CLIENTSTATE_DISCONNECTED); }
162 
163 Configuration Client::recvConfiguration() const {
164  Configuration configuration;
165  if (UA_Client_getState(client_) != UA_CLIENTSTATE_DISCONNECTED) {
166  configuration.ClientRead(client_);
167  }
168  return configuration;
169 }
170 
171 VersionReport Client::recvVersionReport() const {
172  VersionReport version_report;
173  if (UA_Client_getState(client_) != UA_CLIENTSTATE_DISCONNECTED) {
174  version_report.ClientRead(client_);
175  }
176  return version_report;
177 }
178 
179 OriginalManifest Client::recvOriginalManifest() const {
180  OriginalManifest original_manifest;
181  if (UA_Client_getState(client_) != UA_CLIENTSTATE_DISCONNECTED) {
182  original_manifest.ClientRead(client_);
183  }
184  return original_manifest;
185 }
186 
187 bool Client::sendMetadataFiles(std::vector<MetadataFile>& files) const {
188  MetadataFiles metadatafiles;
189  bool retval = true;
190  if (UA_Client_getState(client_) != UA_CLIENTSTATE_DISCONNECTED) {
191  std::random_device rd;
192  std::mt19937 gen(rd());
193  std::uniform_int_distribution<> dis(1, INT_MAX);
194  metadatafiles.setGUID(dis(gen));
195  metadatafiles.setNumberOfMetadataFiles(files.size());
196  metadatafiles.ClientWrite(client_);
197 
198  for (auto f_it = files.begin(); f_it != files.end(); ++f_it) {
199  f_it->setGUID(metadatafiles.getGUID());
200  if (UA_STATUSCODE_GOOD != f_it->ClientWrite(client_)) {
201  retval = false;
202  break;
203  }
204  }
205  }
206  if (!retval && UA_Client_getState(client_) != UA_CLIENTSTATE_DISCONNECTED) {
207  metadatafiles.setNumberOfMetadataFiles(0); // signal cancel to secondary
208  metadatafiles.ClientWrite(client_);
209  }
210  return retval;
211 }
212 
213 bool Client::syncDirectoryFiles(const fs::path& repo_dir) const {
214  if (UA_Client_getState(client_) == UA_CLIENTSTATE_DISCONNECTED) {
215  return false;
216  }
217 
218  opcuabridge::FileList file_list;
219  opcuabridge::FileData file_data(repo_dir);
220 
221  if (UA_STATUSCODE_GOOD != file_list.ClientRead(client_)) {
222  return false;
223  }
224 
225  opcuabridge::FileUnorderedSet file_unordered_set;
226  opcuabridge::UpdateFileUnorderedSet(&file_unordered_set, file_list);
227 
228  bool retval = true;
229  fs::recursive_directory_iterator repo_dir_it_end, repo_dir_it(repo_dir);
230  for (; repo_dir_it != repo_dir_it_end; ++repo_dir_it) {
231  const fs::path& ent_path = repo_dir_it->path();
232  if (fs::is_regular_file(ent_path)) {
233  fs::path rel_path = fs::relative(ent_path, repo_dir);
234  if (file_unordered_set.count(reinterpret_cast<opcuabridge::FileSetEntry>(rel_path.c_str())) == 0) {
235  file_data.setFilePath(rel_path);
236  if (UA_STATUSCODE_GOOD != file_data.ClientWriteFile(client_, ent_path)) {
237  retval = false;
238  break;
239  }
240  }
241  }
242  }
243  file_list.getBlock().resize(1);
244  file_list.getBlock()[0] = '\0';
245  return ((UA_STATUSCODE_GOOD == file_list.ClientWrite(client_)) && retval);
246 }
247 
248 } // namespace opcuabridge