text: // Copyright 2018 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_client.h"
  
  #include <fbl/unique_fd.h>
  #include <fcntl.h>
  #include <fuchsia/hardware/ethertap/cpp/fidl.h>
  #include <lib/async/cpp/wait.h>
  #include <lib/async/default.h>
  #include <lib/fdio/directory.h>
  #include <lib/fit/function.h>
  #include <lib/zx/socket.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <zircon/status.h>
  #include <zircon/types.h>
  
  #include <functional>
  #include <iomanip>
  #include <iostream>
  #include <memory>
  #include <random>
  
  static const char kTapctl[] = "/dev/misc/tapctl";
  
  #define MAC_LOCAL (0x02)
  #define MAC_MULTICAST (0x01)
  
  namespace netemul {
  
  class EthertapClientImpl : public EthertapClient {
   public:
    using TapDevice = fuchsia::hardware::ethertap::TapDevice;
    using TapControl = fuchsia::hardware::ethertap::TapControl;
    explicit EthertapClientImpl(fidl::InterfacePtr<TapDevice> device,
                                EthertapConfig config)
        : config_(std::move(config)), device_(std::move(device)) {
      device_.events().OnFrame = [this](std::vector<uint8_t> data) {
        if (packet_callback_) {
          packet_callback_(std::move(data));
        }
      };
  
      device_.set_error_handler([this](zx_status_t status) {
        fprintf(stderr, "Ethertap device error: %s\n",
                zx_status_get_string(status));
        if (peer_closed_callback_) {
          peer_closed_callback_();
        }
      });
    }
  
    void SetLinkUp(bool linkUp) override { device_->SetOnline(linkUp); }
  
    zx_status_t Send(std::vector<uint8_t> data) override {
      device_->WriteFrame(std::move(data));
      return ZX_OK;
    }
  
    void SetPacketCallback(PacketCallback cb) override {
      packet_callback_ = std::move(cb);
    }
  
    void SetPeerClosedCallback(PeerClosedCallback cb) override {
      peer_closed_callback_ = std::move(cb);
    }
  
    static std::unique_ptr<EthertapClientImpl> Create(
        async_dispatcher_t* dispatcher, EthertapConfig config) {
      zx::socket sock;
  
      fidl::SynchronousInterfacePtr<TapControl> tapctl;
  
      auto status = fdio_service_connect(
          kTapctl, tapctl.NewRequest().TakeChannel().release());
      if (status != ZX_OK) {
        fprintf(stderr, "could not open %s: %s\n", kTapctl,
                zx_status_get_string(status));
        return nullptr;
      }
  
      fidl::InterfacePtr<TapDevice> tapdevice;
  
      zx_status_t o_status = ZX_OK;
  
      status = tapctl->OpenDevice(config.name, config.tap_cfg,
                                  tapdevice.NewRequest(dispatcher), &o_status);
      if (status != ZX_OK) {
        fprintf(stderr, "Could not open tap device: %s\n",
                zx_status_get_string(status));
        return nullptr;
      } else if (o_status != ZX_OK) {
        fprintf(stderr, "Could not open tap device: %s\n",
                zx_status_get_string(o_status));
        return nullptr;
      }
  
      return std::make_unique<EthertapClientImpl>(std::move(tapdevice),
                                                  std::move(config));
    }
  
    void Close() override { device_.Unbind(); }
  
    const zx::channel& channel() override { return device_.channel(); }
  
    EthertapConfig config_;
    fidl::InterfacePtr<TapDevice> device_;
    PacketCallback packet_callback_;
    PeerClosedCallback peer_closed_callback_;
  };
  
  std::unique_ptr<EthertapClient> EthertapClient::Create(
      EthertapConfig config, async_dispatcher_t* dispatcher) {
    if (dispatcher == nullptr) {
      dispatcher = async_get_default_dispatcher();
    }
    return EthertapClientImpl::Create(dispatcher, std::move(config));
  }
  
  void EthertapConfig::RandomLocalUnicast(const std::string& str_seed) {
    std::vector<uint8_t> sseed(str_seed.begin(), str_seed.end());
    std::random_device rd;
    // Add some randomness to the name from random_device
    // as a temporary fix due to ethertap devfs entries being leaked
    // across test boundaries (which caused tests to fail).
    // TODO(brunodalbo) go back to only the string seed
    //  once ZX-2956 is fixed.
    sseed.push_back(rd());
    sseed.push_back(rd());
    sseed.push_back(rd());
    sseed.push_back(rd());
    std::seed_seq seed(sseed.begin(), sseed.end());
    std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t>
        rnd(seed);
    std::generate(tap_cfg.mac.octets.begin(), tap_cfg.mac.octets.end(), rnd);
    // set unicast:
    SetMacUnicast();
    SetMacLocallyAdministered();
  }
  
  void EthertapConfig::SetMacUnicast() {
    tap_cfg.mac.octets[0] &= ~(MAC_MULTICAST);
  }
  
  void EthertapConfig::SetMacLocallyAdministered() {
    tap_cfg.mac.octets[0] |= MAC_LOCAL;
  }
  
  bool EthertapConfig::IsMacLocallyAdministered() {
    return (tap_cfg.mac.octets[0] & MAC_LOCAL) != 0;
  }
  
  }  // namespace netemul
  