text: // Copyright 2019 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 "device.h"
  
  #include <stdio.h>
  #include <zircon/errors.h>
  
  #include <ddk/debug.h>
  
  namespace wlan::simulation {
  
  FakeDevMgr::FakeDevMgr() : fake_root_dev_id_(1) {
    // This is the fake root device, which is the parent of all first-level devices added to
    // FakeDevMgr.
    wlan_sim_dev_info_t fake_root = {
        // Parent of fake root device is nullptr(corresponding to device number 0)
        .parent = nullptr,
        .ref_count = 1,
    };
  
    devices_.insert({fake_root_dev_id_, fake_root});
  }
  
  FakeDevMgr::~FakeDevMgr() {
    auto root_iter = devices_.find(fake_root_dev_id_);
    if (root_iter == devices_.end()) {
      DBG_PRT("%s Fake root device is missing.\n" __func__);
      return;
    }
  
    auto iter = devices_.begin();
    while (iter != devices_.end()) {
      const auto next = std::next(iter);
      const auto& dev_info = iter->second;
      if (dev_info.parent == fake_root_dev_id_.as_device()) {
        if (DeviceUnreference(iter)) {
          root_iter->second.ref_count--;
        }
      }
      iter = next;
    }
  
    // The fake root device starts with a ref_count of 1; if it does not finish with a ref_count
    // of 1, we are leaking a child device.
    if (root_iter->second.ref_count != 1) {
      DBG_PRT("%s The root device ref count is: %d, it should be 1 when destructing FakeDevMgr.\n",
              __func__, root_iter->second.ref_count);
    }
    // The only remaining device in the device tree should be the fake root.  Any outstanding
    // child devices are leaks.
    if (devices_.size() != 1) {
      DBG_PRT("%s The number of devices is %d, only fake root device should be left at this point.\n",
              __func__, devices_.size());
    }
  }
  
  zx_status_t FakeDevMgr::DeviceAdd(zx_device_t* parent, device_add_args_t* args, zx_device_t** out) {
    // We use refcounting to maintain the fake device tree, and do not invoke the unbind hook.
    if (args->ops) {
      ZX_ASSERT(args->ops->unbind == nullptr);
    }
    wlan_sim_dev_info_t dev_info = {
        .parent = parent,
        .dev_args = (args == nullptr ? device_add_args_t{} : *args),
        .ref_count = 1,
    };
    DeviceId id(dev_counter_++);
    devices_.insert({id, dev_info});
  
    auto iter = devices_.find(DeviceId::FromDevice(parent));
    if (iter == devices_.end()) {
      DBG_PRT("%s Parent does not exist, might be root device.\n", __func__);
    } else {
      (iter->second).ref_count++;
    }
  
    if (out) {
      *out = id.as_device();
    }
  
    DBG_PRT("%s: Added SIM device. proto %d # devices: %lu Handle: %p\n", __func__,
            args ? args->proto_id : 0, devices_.size(), out ? *out : nullptr);
    return ZX_OK;
  }
  
  void FakeDevMgr::DeviceAsyncRemove(zx_device_t* device) {
    auto iter = devices_.find(DeviceId::FromDevice(device));
    if (iter == devices_.end()) {
      DBG_PRT("%s device %p does not exist\n", __func__, device);
      return;
    }
  
    while (true) {
      const auto parent_device = iter->second.parent;
      if (!DeviceUnreference(iter)) {
        // This device isn't being removed, so we don't need to unreference its parent.
        return;
      }
      iter = devices_.find(DeviceId::FromDevice(parent_device));
      if (iter == devices_.end()) {
        DBG_PRT("%s device %p does not exist, we might reach the root\n", __func__, device);
        return;
      }
    }
  }
  
  bool FakeDevMgr::DeviceUnreference(devices_t::iterator iter) {
    if (iter->second.ref_count == 0) {
      DBG_PRT("Error: device %s already has zero refcount\n", iter->second.dev_args.name);
      return false;
    }
  
    iter->second.ref_count--;
    if (iter->second.ref_count == 0) {
      const auto args = iter->second.dev_args;
      if (args.ops && args.ops->release) {
        (*args.ops->release)(args.ctx);
      }
      devices_.erase(iter);
      // Device released
      return true;
    }
    // Just decrease ref_count, not actually released
    return false;
  }
  
  std::optional<wlan_sim_dev_info_t> FakeDevMgr::FindFirst(const Predicate& pred) {
    auto iter = std::find_if(begin(), end(), [pred](auto& entry) -> bool {
      auto& [dev, dev_info] = entry;
      return pred(dev, dev_info);
    });
    if (iter == end()) {
      return {};
    }
    return {iter->second};
  }
  
  std::optional<wlan_sim_dev_info_t> FakeDevMgr::FindFirstByProtocolId(uint32_t proto_id) {
    return FindFirst(
        [proto_id](auto _dev, auto& dev_info) { return proto_id == dev_info.dev_args.proto_id; });
  }
  
  std::optional<wlan_sim_dev_info_t> FakeDevMgr::GetDevice(zx_device_t* device) {
    return FindFirst([device](auto dev, auto& dev_info) { return device == dev.as_device(); });
  }
  
  // Return the number of devices other than fake root device.
  size_t FakeDevMgr::DeviceCount() { return devices_.size() - 1; }
  
  zx_device_t* FakeDevMgr::GetRootDevice() { return fake_root_dev_id_.as_device(); }
  
  }  // namespace wlan::simulation
  