| // 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 "ethertap.h" | 
 |  | 
 | #include <lib/fake_ddk/fake_ddk.h> | 
 | #include <lib/zx/process.h> | 
 |  | 
 | #include <string> | 
 | #include <thread> | 
 | #include <vector> | 
 |  | 
 | #include <ddk/debug.h> | 
 | #include <ddk/device.h> | 
 | #include <zxtest/zxtest.h> | 
 |  | 
 | class FakeEthertapMiscParent : public ddk::Device<FakeEthertapMiscParent>, public fake_ddk::Bind { | 
 |  public: | 
 |   FakeEthertapMiscParent() : ddk::Device<FakeEthertapMiscParent>(fake_ddk::kFakeParent) {} | 
 |  | 
 |   ~FakeEthertapMiscParent() { | 
 |     WaitForChildRemoval(); | 
 |     if (tap_ctl_) { | 
 |       tap_ctl_->DdkRelease(); | 
 |     } | 
 |     if (child_device_) { | 
 |       child_device_->device->DdkRelease(); | 
 |     } | 
 |   } | 
 |  | 
 |   // Returns immediately if this was called previously. | 
 |   zx_status_t WaitForChildRemoval() { | 
 |     if (child_device_) { | 
 |       return sync_completion_wait(&child_device_->completion, ZX_TIME_INFINITE); | 
 |     } | 
 |     return ZX_OK; | 
 |   } | 
 |  | 
 |   zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args, | 
 |                         zx_device_t** out) override { | 
 |     if (parent == fake_ddk::kFakeParent) { | 
 |       tap_ctl_ = static_cast<eth::TapCtl*>(args->ctx); | 
 |     } else if (parent == tap_ctl_->zxdev()) { | 
 |       if (child_device_) { | 
 |         ADD_FAILURE("Unexpected additional child device added"); | 
 |         return ZX_ERR_INTERNAL; | 
 |       } | 
 |       child_device_ = ChildDevice(); | 
 |       child_device_->name = args->name; | 
 |       child_device_->device = static_cast<eth::TapDevice*>(args->ctx); | 
 |       // Set the device's unbind hook that fake_ddk::Bind::DeviceAsyncRemove will call. | 
 |       unbind_op_ = args->ops->unbind; | 
 |       op_ctx_ = args->ctx; | 
 |     } else { | 
 |       ADD_FAILURE("Unrecognized parent"); | 
 |     } | 
 |     *out = reinterpret_cast<zx_device_t*>(args->ctx); | 
 |     return ZX_OK; | 
 |   } | 
 |  | 
 |   zx_status_t DeviceRemove(zx_device_t* device) override { | 
 |     if (child_device_ && reinterpret_cast<zx_device_t*>(child_device_->device) == device) { | 
 |       sync_completion_signal(&child_device_->completion); | 
 |     } | 
 |     return ZX_OK; | 
 |   } | 
 |  | 
 |   eth::TapCtl* tap_ctl() { return tap_ctl_; } | 
 |  | 
 |   eth::TapDevice* tap_device() { return child_device_->device; } | 
 |  | 
 |   void DdkRelease() {} | 
 |  | 
 |   const char* DeviceGetName(zx_device_t* device) override { | 
 |     if (device == tap_ctl_->zxdev()) { | 
 |       return "tapctl"; | 
 |     } | 
 |     if (child_device_ && child_device_->device->zxdev() == device) { | 
 |       return child_device_->name.c_str(); | 
 |     } | 
 |     return ""; | 
 |   } | 
 |  | 
 |  private: | 
 |   struct ChildDevice { | 
 |     std::string name; | 
 |     eth::TapDevice* device; | 
 |     sync_completion_t completion; | 
 |   }; | 
 |  | 
 |   eth::TapCtl* tap_ctl_; | 
 |   std::optional<ChildDevice> child_device_; | 
 | }; | 
 |  | 
 | class EthertapTests : public zxtest::Test { | 
 |  public: | 
 |   EthertapTests() { | 
 |     fbl::Array<fake_ddk::ProtocolEntry> protocols(new fake_ddk::ProtocolEntry[1], 1); | 
 |     protocols[0] = {ZX_PROTOCOL_MISC_PARENT, {nullptr, nullptr}}; | 
 |     ddk_.SetProtocols(std::move(protocols)); | 
 |   } | 
 |  | 
 |   void SetupTapCtlMessenger() { | 
 |     messenger_.SetMessageOp(ddk_.tap_ctl(), | 
 |                             [](void* ctx, fidl_incoming_msg_t* msg, fidl_txn_t* txn) { | 
 |                               return static_cast<eth::TapCtl*>(ctx)->DdkMessage(msg, txn); | 
 |                             }); | 
 |   } | 
 |  | 
 |  protected: | 
 |   FakeEthertapMiscParent ddk_; | 
 |   fake_ddk::FidlMessenger messenger_; | 
 | }; | 
 |  | 
 | TEST_F(EthertapTests, TestLongNameMatches) { | 
 |   ASSERT_OK(eth::TapCtl::Create(nullptr, fake_ddk::kFakeParent)); | 
 |   SetupTapCtlMessenger(); | 
 |   const char* long_name = "012345678901234567890123456789"; | 
 |   auto len = strlen(long_name); | 
 |   ASSERT_EQ(len, fuchsia_hardware_ethertap_MAX_NAME_LENGTH); | 
 |   fuchsia_hardware_ethertap_Config config = {0, 0, 1500, {1, 2, 3, 4, 5, 6}}; | 
 |   zx::channel tap, req; | 
 |   ASSERT_OK(zx::channel::create(0, &tap, &req)); | 
 |   zx_status_t status; | 
 |   ASSERT_OK(fuchsia_hardware_ethertap_TapControlOpenDevice(messenger_.local().get(), long_name, len, | 
 |                                                            &config, tap.release(), &status)); | 
 |   ASSERT_OK(status); | 
 |   ASSERT_STR_EQ(long_name, ddk_.tap_device()->name()); | 
 | } | 
 |  | 
 | TEST_F(EthertapTests, TestShortNameMatches) { | 
 |   ASSERT_OK(eth::TapCtl::Create(nullptr, fake_ddk::kFakeParent)); | 
 |   SetupTapCtlMessenger(); | 
 |   const char* short_name = "abc"; | 
 |   auto len = strlen(short_name); | 
 |   fuchsia_hardware_ethertap_Config config = {0, 0, 1500, {1, 2, 3, 4, 5, 6}}; | 
 |   zx::channel tap, req; | 
 |   ASSERT_OK(zx::channel::create(0, &tap, &req)); | 
 |   zx_status_t status; | 
 |   ASSERT_OK(fuchsia_hardware_ethertap_TapControlOpenDevice(messenger_.local().get(), short_name, | 
 |                                                            len, &config, tap.release(), &status)); | 
 |   ASSERT_OK(status); | 
 |   ASSERT_STR_EQ(short_name, ddk_.tap_device()->name()); | 
 | } | 
 |  | 
 | // This tests triggering the unbind hook via DdkAsyncRemove and verifying the unbind reply occurs. | 
 | TEST_F(EthertapTests, UnbindSignalsWorkerThread) { | 
 |   ASSERT_OK(eth::TapCtl::Create(nullptr, fake_ddk::kFakeParent)); | 
 |   SetupTapCtlMessenger(); | 
 |   fuchsia_hardware_ethertap_Config config = {0, 0, 1500, {1, 2, 3, 4, 5, 6}}; | 
 |   zx::channel tap, req; | 
 |   ASSERT_OK(zx::channel::create(0, &tap, &req)); | 
 |   zx_status_t status; | 
 |   ASSERT_OK(fuchsia_hardware_ethertap_TapControlOpenDevice(messenger_.local().get(), "", 0, &config, | 
 |                                                            tap.release(), &status)); | 
 |   ASSERT_OK(status); | 
 |  | 
 |   // This should run the device unbind hook, which signals the worker thread to reply to the | 
 |   // unbind txn and exit. | 
 |   ddk_.tap_device()->DdkAsyncRemove(); | 
 |   // |DeviceRemove| should be called after the unbind txn is replied to. | 
 |   ASSERT_OK(ddk_.WaitForChildRemoval()); | 
 | } |