| // Copyright 2020 The Fuchsia Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "src/developer/forensics/feedback_data/device_id_provider.h" | 
 |  | 
 | #include <lib/syslog/cpp/macros.h> | 
 |  | 
 | #include <memory> | 
 | #include <optional> | 
 | #include <tuple> | 
 | #include <utility> | 
 |  | 
 | #include "src/developer/forensics/utils/errors.h" | 
 | #include "src/lib/files/directory.h" | 
 | #include "src/lib/files/file.h" | 
 | #include "src/lib/fxl/strings/string_printf.h" | 
 | #include "src/lib/uuid/uuid.h" | 
 |  | 
 | namespace forensics { | 
 | namespace feedback_data { | 
 | namespace { | 
 |  | 
 | // Server for |fuchsia.feedback.DeviceIdProvider| that only responds to the first call to GetId as | 
 | // the ID never changes and the method is a hanging get. | 
 | class DeviceIdProvider : public fuchsia::feedback::DeviceIdProvider { | 
 |  public: | 
 |   DeviceIdProvider(async_dispatcher_t* dispatcher, | 
 |                    ::fidl::InterfaceRequest<fuchsia::feedback::DeviceIdProvider> request, | 
 |                    ::fit::function<void(zx_status_t)> on_channel_close, std::string* device_id) | 
 |       : connection_(this, std::move(request), dispatcher), | 
 |         has_been_called_(false), | 
 |         device_id_(device_id) { | 
 |     connection_.set_error_handler(std::move(on_channel_close)); | 
 |   } | 
 |  | 
 |   // |fuchsia.feedback.DeviceIdProvider| | 
 |   void GetId(GetIdCallback callback) override { | 
 |     // This is the second call on this connection and we want to leave it hanging forever. | 
 |     if (has_been_called_) { | 
 |       return; | 
 |     } | 
 |  | 
 |     callback(*device_id_); | 
 |     has_been_called_ = true; | 
 |   } | 
 |  | 
 |  private: | 
 |   ::fidl::Binding<fuchsia::feedback::DeviceIdProvider> connection_; | 
 |  | 
 |   bool has_been_called_; | 
 |   std::string* device_id_; | 
 | }; | 
 |  | 
 | // Reads a device id from the file at |path|. If the device id doesn't exist or is invalid, return | 
 | // a nullopt. | 
 | std::optional<std::string> ReadDeviceId(const std::string& path) { | 
 |   std::string id; | 
 |   if (!files::ReadFileToString(path, &id)) { | 
 |     return std::nullopt; | 
 |   } | 
 |  | 
 |   return id; | 
 | } | 
 |  | 
 | // Creates a new device id and stores it at |path|. | 
 | // | 
 | // The id is a 128-bit (pseudo) random UUID in the form of version 4 as described in RFC 4122, | 
 | // section 4.4. | 
 | std::string InitializeDeviceId(const std::string& path) { | 
 |   if (const auto read_id = ReadDeviceId(path); | 
 |       read_id.has_value() && uuid::IsValid(read_id.value())) { | 
 |     return read_id.value(); | 
 |   } | 
 |  | 
 |   std::string new_id = uuid::Generate(); | 
 |   if (!files::WriteFile(path, new_id.c_str(), new_id.size())) { | 
 |     FX_LOGS(ERROR) << fxl::StringPrintf("Cannot write device id '%s' to '%s'", new_id.c_str(), | 
 |                                         path.c_str()); | 
 |   } | 
 |  | 
 |   FX_LOGS(INFO) << "Created new feedback device id"; | 
 |   return new_id; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | DeviceIdManager::DeviceIdManager(async_dispatcher_t* dispatcher, const std::string& path) | 
 |     : dispatcher_(dispatcher), device_id_(InitializeDeviceId(path)), next_provider_idx_(0u) {} | 
 |  | 
 | void DeviceIdManager::AddBinding( | 
 |     ::fidl::InterfaceRequest<fuchsia::feedback::DeviceIdProvider> request, | 
 |     ::fit::function<void(zx_status_t)> on_channel_close) { | 
 |   const size_t idx = next_provider_idx_++; | 
 |   providers_.emplace( | 
 |       idx, | 
 |       std::make_unique<DeviceIdProvider>( | 
 |           dispatcher_, std::move(request), | 
 |           [this, idx, on_channel_close = std::move(on_channel_close)](const zx_status_t status) { | 
 |             // Execute |on_channel_close| before removing the created DeviceIdProvider from | 
 |             // |providers_|. | 
 |             on_channel_close(status); | 
 |             providers_.erase(idx); | 
 |           }, | 
 |           &device_id_)); | 
 | } | 
 |  | 
 | }  // namespace feedback_data | 
 | }  // namespace forensics |