| // 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 "fake_netstack.h" |
| |
| #include <fuchsia/net/interfaces/cpp/fidl.h> |
| #include <fuchsia/net/virtualization/cpp/fidl.h> |
| #include <fuchsia/netstack/cpp/fidl_test_base.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/executor.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/cpp/wait.h> |
| #include <lib/async/default.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/fit/defer.h> |
| #include <lib/fit/thread_checker.h> |
| #include <lib/fpromise/bridge.h> |
| #include <lib/fpromise/promise.h> |
| #include <lib/fpromise/scope.h> |
| #include <lib/fpromise/single_threaded_executor.h> |
| #include <lib/sys/cpp/testing/enclosing_environment.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <netinet/if_ether.h> |
| #include <netinet/ip.h> |
| #include <netinet/udp.h> |
| #include <zircon/device/ethernet.h> |
| #include <zircon/status.h> |
| |
| #include <future> |
| #include <queue> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/connectivity/lib/network-device/cpp/network_device_client.h" |
| |
| namespace { |
| |
| using network::client::NetworkDeviceClient; |
| |
| using Packet = std::vector<uint8_t>; |
| |
| constexpr uint32_t kMtu = 1500; |
| |
| constexpr uint8_t kHostMacAddress[ETH_ALEN] = {0x02, 0x1a, 0x11, 0x00, 0x00, 0x00}; |
| |
| constexpr uint8_t kHostIpv4Address[4] = {192, 168, 0, 1}; |
| constexpr uint8_t kGuestIpv4Address[4] = {192, 168, 0, 10}; |
| |
| constexpr uint16_t kProtocolIpv4 = 0x0800; |
| constexpr uint8_t kPacketTypeUdp = 17; |
| constexpr uint16_t kTestPort = 4242; |
| |
| // Compare MacAddress using lexicographical ordering. |
| struct MacAddressComparator { |
| bool operator()(const fuchsia::hardware::ethernet::MacAddress& a, |
| const fuchsia::hardware::ethernet::MacAddress& b) const { |
| return std::lexicographical_compare(a.octets.begin(), a.octets.end(), b.octets.begin(), |
| b.octets.end()); |
| } |
| }; |
| |
| // Calculate the IPv4 checksum of the given packet. |
| uint16_t Checksum(const void* _data, size_t len, uint16_t _sum) { |
| uint32_t sum = _sum; |
| auto data = static_cast<const uint16_t*>(_data); |
| for (; len > 1; len -= 2) { |
| sum += *data++; |
| } |
| if (len) { |
| sum += (*data & UINT8_MAX); |
| } |
| while (sum > UINT16_MAX) { |
| sum = (sum & UINT16_MAX) + (sum >> 16); |
| } |
| return static_cast<uint16_t>(~sum); |
| } |
| |
| // Copy the data out of the given NetworkDeviceClient::Buffer. |
| Packet CopyPacketFromBuffer(NetworkDeviceClient::Buffer& buffer) { |
| Packet result(buffer.data().len()); |
| size_t copied = buffer.data().Read(result.data(), result.size()); |
| FX_CHECK(copied == result.size()) << "Expected " << result.size() << " byte(s) to be copied, but " |
| << copied << " byte(s) copied."; |
| return result; |
| } |
| |
| // Run the given functions on the given dispatcher, blocking until the lambda |
| // has completed. |
| // |
| // Will deadlock if the current thread is already running on `dispatcher`, and |
| // no other threads are available. |
| void RunOnExecutorSync(async::Executor& executor, fit::function<void()> workload) { |
| fpromise::run_single_threaded( |
| fpromise::schedule_for_consumer(&executor, fpromise::make_promise(std::move(workload))) |
| .promise()); |
| } |
| |
| } // namespace |
| |
| namespace fake_netstack::internal { |
| |
| // A network device connected to the fake network. |
| // |
| // Thread hostile: construction and methods should all be called on the thread |
| // backing the single-threaded `executor`. |
| class Device : public fuchsia::net::virtualization::Interface { |
| public: |
| // Create and configure a new device. |
| // |
| // The returned promise is resolved once the device has been configured and |
| // is ready to send and receive packets. |
| static fpromise::promise<std::unique_ptr<Device>, zx_status_t> Create( |
| async::Executor* executor, fidl::InterfaceHandle<fuchsia::hardware::network::Port> port, |
| fidl::InterfaceRequest<fuchsia::net::virtualization::Interface> interface) { |
| // State persisting across future executions. |
| struct State { |
| fuchsia::hardware::network::PortPtr port_ptr; |
| fidl::InterfaceHandle<fuchsia::hardware::network::Device> device_handle; |
| fuchsia_hardware_network::wire::PortId port_id; |
| std::unique_ptr<Device> device; |
| }; |
| auto state = std::make_unique<State>(); |
| |
| // Establish a connection to the Device backing the the Port. |
| state->port_ptr = port.Bind(); |
| state->port_ptr->GetDevice(state->device_handle.NewRequest()); |
| |
| // Fetch the port's ID. |
| fpromise::bridge<void, zx_status_t> bridge; |
| auto completer = |
| std::make_shared<fpromise::completer<void, zx_status_t>>(std::move(bridge.completer)); |
| state->port_ptr.set_error_handler( |
| [completer](zx_status_t status) { completer->complete_error(status); }); |
| state->port_ptr->GetInfo([completer = std::move(completer), |
| state = state.get()](fuchsia::hardware::network::PortInfo info) { |
| state->port_id = { |
| .base = info.id().base, |
| .salt = info.id().salt, |
| }; |
| completer->complete_ok(); |
| }); |
| |
| // Create the device object. |
| auto create_device = [state = state.get(), interface = std::move(interface), |
| executor]() mutable { |
| // Create the Device object. |
| // |
| // Using `new` to allow access to private constructor. |
| state->device = std::unique_ptr<Device>(new Device( |
| executor->dispatcher(), std::move(state->device_handle), std::move(interface))); |
| return fpromise::ok(); |
| }; |
| |
| // Get the client's MAC address. |
| auto fetch_device_mac_address = [state = state.get()]() { |
| fpromise::bridge<void, zx_status_t> bridge; |
| std::lock_guard guard(state->device->checker_); |
| state->device->client_.GetPortInfoWithMac( |
| state->port_id, [state, completer = std::move(bridge.completer)]( |
| zx::status<network::client::PortInfoAndMac> result) mutable { |
| if (result.is_error()) { |
| FX_PLOGS(WARNING, result.status_value()) << "Could not fetch device port information"; |
| completer.complete_error(result.status_value()); |
| return; |
| } |
| state->device->port_info_ = std::move(result.value()); |
| completer.complete_ok(); |
| }); |
| return bridge.consumer.promise(); |
| }; |
| |
| // Open a session so the device is ready to use. |
| auto open_session = [state = state.get()]() mutable -> fpromise::promise<void, zx_status_t> { |
| std::lock_guard guard(state->device->checker_); |
| fpromise::bridge<void, zx_status_t> bridge; |
| state->device->client_.OpenSession( |
| "fake-netstack-session", |
| [completer = std::move(bridge.completer)](zx_status_t status) mutable { |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Error opening device session"; |
| completer.complete_error(status); |
| return; |
| } |
| completer.complete_ok(); |
| }); |
| return bridge.consumer.promise(); |
| }; |
| |
| // Open the requested port. |
| auto attach_port = [state = state.get()]() -> fpromise::promise<void, zx_status_t> { |
| std::lock_guard guard(state->device->checker_); |
| fpromise::bridge<void, zx_status_t> bridge; |
| state->device->client_.AttachPort( |
| state->port_id, {fuchsia_hardware_network::wire::FrameType::kEthernet}, |
| [completer = std::move(bridge.completer)](zx_status_t status) mutable { |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Error attaching to device port"; |
| completer.complete_error(status); |
| return; |
| } |
| completer.complete_ok(); |
| }); |
| return bridge.consumer.promise(); |
| }; |
| |
| // Return the completed device to caller. |
| auto return_device = |
| [state = state.get()]() -> fpromise::result<std::unique_ptr<Device>, zx_status_t> { |
| return fpromise::ok(std::move(state->device)); |
| }; |
| |
| return bridge.consumer.promise() |
| .and_then(std::move(create_device)) |
| .and_then(std::move(fetch_device_mac_address)) |
| .and_then(std::move(open_session)) |
| .and_then(std::move(attach_port)) |
| .and_then(std::move(return_device)) |
| // Keep `state` alive until future is resolved. |
| .inspect([state = std::move(state)]( |
| const fpromise::result<std::unique_ptr<Device>, zx_status_t>&) {}); |
| } |
| |
| const network::client::PortInfoAndMac& port_info() { return port_info_; } |
| |
| // Read the first available packet received by the device. |
| fpromise::promise<Packet, zx_status_t> ReadPacket() { |
| std::lock_guard guard(checker_); |
| |
| // If there is already a packet waiting, just return it directly. |
| if (!packets_.empty()) { |
| Packet result = std::move(packets_.front()); |
| packets_.pop(); |
| return fpromise::make_result_promise<Packet, zx_status_t>(fpromise::ok(std::move(result))); |
| } |
| |
| // Otherwise, wait the next packet arrives. |
| fpromise::bridge<Packet, zx_status_t> bridge; |
| waiters_.push(std::move(bridge.completer)); |
| return bridge.consumer.promise(); |
| } |
| |
| // Transmit a packet over the device. |
| fpromise::promise<void, zx_status_t> WritePacket(Packet payload) { |
| std::lock_guard guard(checker_); |
| |
| // Allocate a buffer. |
| NetworkDeviceClient::Buffer buffer = client_.AllocTx(); |
| if (!buffer.is_valid()) { |
| return fpromise::make_result_promise<void, zx_status_t>(fpromise::error(ZX_ERR_NO_RESOURCES)); |
| } |
| |
| // Set up metadata and copy the data. |
| buffer.data().SetFrameType(fuchsia_hardware_network::wire::FrameType::kEthernet); |
| buffer.data().SetPortId(port_info_.id); |
| size_t transmitted = buffer.data().Write(payload.data(), payload.size()); |
| FX_CHECK(transmitted == payload.size()) |
| << "Expected " << payload.size() << " byte(s) to be transmitted, but only " << transmitted |
| << " byte(s) were."; |
| |
| // Send the packet. |
| zx_status_t status = buffer.Send(); |
| if (status != ZX_OK) { |
| return fpromise::make_result_promise<void, zx_status_t>(fpromise::error(status)); |
| } |
| |
| return fpromise::make_result_promise<void, zx_status_t>(fpromise::ok()); |
| } |
| |
| private: |
| Device(async_dispatcher_t* dispatcher, |
| fidl::InterfaceHandle<fuchsia::hardware::network::Device> device_server, |
| fidl::InterfaceRequest<fuchsia::net::virtualization::Interface> interface) |
| : client_(fidl::ClientEnd<fuchsia_hardware_network::Device>(device_server.TakeChannel()), |
| dispatcher), |
| interface_(this, std::move(interface), dispatcher) { |
| // Register for error notifications. |
| client_.SetErrorCallback([this](zx_status_t status) { |
| std::lock_guard guard(checker_); |
| FX_LOGS(WARNING) << "Ethernet client registered error: " << zx_status_get_string(status); |
| interface_.Close(status); |
| }); |
| // Register for packet arrivals. |
| client_.SetRxCallback( |
| [this](NetworkDeviceClient::Buffer buffer) { PacketReceived(std::move(buffer)); }); |
| } |
| |
| // Called when a packet has been received by the device. |
| void PacketReceived(NetworkDeviceClient::Buffer buffer) { |
| std::lock_guard guard(checker_); |
| |
| // If nobody is waiting for a packet, simply add the packet to the queue. |
| if (waiters_.empty()) { |
| packets_.push(CopyPacketFromBuffer(buffer)); |
| return; |
| } |
| |
| // Otherwise, give the packet to a waiter. |
| waiters_.front().complete_ok(CopyPacketFromBuffer(buffer)); |
| waiters_.pop(); |
| } |
| |
| fit::thread_checker checker_; |
| NetworkDeviceClient client_ __TA_GUARDED(checker_); |
| fidl::Binding<fuchsia::net::virtualization::Interface> interface_ __TA_GUARDED(checker_); |
| network::client::PortInfoAndMac port_info_ = {}; |
| |
| std::queue<Packet> packets_ __TA_GUARDED(checker_); // Received packets |
| std::queue<fpromise::completer<Packet, zx_status_t>> waiters_ |
| __TA_GUARDED(checker_); // Waiters for packets |
| }; |
| |
| // A fake network, consisting of one or more devices. |
| // |
| // This class implements both the `fuchsia.net.virtualization/Control` and |
| // `fuchsia.net.virtualization/Network` FIDL protocols. |
| // |
| // The Control API is able to create multiple independent networks, but this |
| // class simply maps them all into a single network for simplicity. |
| // |
| // Thread hostile: unless specified otherwise, construction, |
| // destruction, and methods should all be called on the thread backing |
| // the single-threaded `executor`. |
| class FakeNetwork : public fuchsia::net::virtualization::Control, |
| public fuchsia::net::virtualization::Network { |
| public: |
| explicit FakeNetwork(async::Executor* executor) : executor_(executor) {} |
| ~FakeNetwork() override { FX_DCHECK(checker_.is_thread_valid()); } |
| |
| // Get an interface request handler. |
| // |
| // This method and the returned handler are thread safe. |
| fidl::InterfaceRequestHandler<fuchsia::net::virtualization::Control> GetHandler() { |
| return [this](fidl::InterfaceRequest<fuchsia::net::virtualization::Control> request) { |
| async::PostTask(executor_->dispatcher(), [request = std::move(request), this]() mutable { |
| std::lock_guard guard(checker_); |
| control_bindings_.AddBinding(this, std::move(request), executor_->dispatcher()); |
| }); |
| }; |
| } |
| |
| // Wait for a device with the given MAC address to be added to this network, |
| // and then return it. |
| fpromise::promise<Device*, zx_status_t> GetDevice( |
| const fuchsia::hardware::ethernet::MacAddress& mac_addr) { |
| std::lock_guard guard(checker_); |
| |
| // If the device is already connected the the netstack then just return |
| // a pointer to it. |
| auto it = devices_.find(mac_addr); |
| if (it != devices_.end()) { |
| return fpromise::make_result_promise<Device*, zx_status_t>(fpromise::ok(it->second.get())); |
| } |
| |
| // Otherwise, add to the list of completers for this MAC address. The |
| // promise will complete when the devices calls AddEthernetDevice. |
| fpromise::bridge<Device*, zx_status_t> bridge; |
| auto completers_it = completers_.find(mac_addr); |
| if (completers_it == completers_.end()) { |
| std::vector<fpromise::completer<Device*, zx_status_t>> vec; |
| vec.push_back(std::move(bridge.completer)); |
| completers_.insert(std::make_pair(mac_addr, std::move(vec))); |
| } else { |
| completers_it->second.push_back(std::move(bridge.completer)); |
| } |
| |
| return bridge.consumer.promise(); |
| } |
| |
| // `fuchsia.net.virtualization/Control` implementation. |
| void CreateNetwork( |
| fuchsia::net::virtualization::Config config, |
| fidl::InterfaceRequest<fuchsia::net::virtualization::Network> network) override { |
| std::lock_guard guard(checker_); |
| |
| // We only support bridged connections. |
| if (!config.is_bridged()) { |
| FX_LOGS(ERROR) << "FakeNetstack only supports bridged connections. Received: " |
| << config.Ordinal(); |
| network.Close(ZX_ERR_NOT_SUPPORTED); |
| return; |
| } |
| |
| // Create a new network. |
| network_bindings_.AddBinding(this, std::move(network), executor_->dispatcher()); |
| } |
| |
| // `fuchsia.net.virtualization/Network` implementation. |
| void AddPort(fidl::InterfaceHandle<fuchsia::hardware::network::Port> port, |
| fidl::InterfaceRequest<fuchsia::net::virtualization::Interface> interface) override { |
| std::lock_guard guard(checker_); |
| |
| // Create the device and track it. |
| fpromise::schedule_for_consumer(executor_, |
| Device::Create(executor_, std::move(port), std::move(interface)) |
| .and_then([this](std::unique_ptr<Device>& device) { |
| AddReadyDevice(std::move(device)); |
| }) |
| .wrap_with(scope_)); |
| } |
| |
| private: |
| // Add the given device to this network. |
| void AddReadyDevice(std::unique_ptr<Device> device) { |
| std::lock_guard guard(checker_); |
| |
| // Get the device's MAC address, aborting if one does not exist. |
| if (!device->port_info().unicast_address.has_value()) { |
| FX_LOGS(WARNING) << "Ignoring attempt to add device without a MAC address"; |
| return; |
| } |
| fuchsia::hardware::ethernet::MacAddress device_mac; |
| memcpy(&device_mac.octets[0], device->port_info().unicast_address->octets.data(), |
| sizeof(device_mac.octets)); |
| |
| // Add the device. |
| auto [it, success] = devices_.insert(std::make_pair(device_mac, std::move(device))); |
| if (!success) { |
| FX_LOGS(WARNING) << "Ignoring attempt to add device with duplicate MAC address"; |
| return; |
| } |
| |
| // Resolve any pending promises to fetch this device. |
| auto completers_it = completers_.find(device_mac); |
| if (completers_it == completers_.end()) { |
| return; |
| } |
| while (!completers_it->second.empty()) { |
| completers_it->second.back().complete_ok(it->second.get()); |
| completers_it->second.pop_back(); |
| } |
| } |
| |
| fit::thread_checker checker_; |
| |
| async::Executor* executor_; // Owned elsewhere. |
| |
| fidl::BindingSet<fuchsia::net::virtualization::Control> control_bindings_ __TA_GUARDED(checker_); |
| fidl::BindingSet<fuchsia::net::virtualization::Network> network_bindings_ __TA_GUARDED(checker_); |
| |
| // Maps MAC addresses to devices. |
| std::map<fuchsia::hardware::ethernet::MacAddress, std::unique_ptr<Device>, MacAddressComparator> |
| devices_ __TA_GUARDED(checker_); |
| |
| // Maps MAC addresses to completers, to enable the GetDevice promises. |
| std::map<fuchsia::hardware::ethernet::MacAddress, |
| std::vector<fpromise::completer<Device*, zx_status_t>>, MacAddressComparator> |
| completers_ __TA_GUARDED(checker_); |
| |
| fpromise::scope scope_ __TA_GUARDED(checker_); |
| }; |
| |
| } // namespace fake_netstack::internal |
| |
| FakeNetstack::FakeNetstack() |
| : loop_(&kAsyncLoopConfigNoAttachToCurrentThread), executor_(loop_.dispatcher()) { |
| // Start a thread for the Device waiters. |
| // |
| // We can't use the main test thread, because it will block to run test |
| // utils and deadlock the test. |
| loop_.StartThread("fake-netstack-v2-thread"); |
| |
| // Construct the FakeNetwork on the dispatcher thread. |
| // |
| // FakeNetwork has thread-hostile components, so we ensure that |
| // construction/destruction/method calls all occur on the executor's thread. |
| RunOnExecutorSync(executor_, [this] { |
| network_ = std::make_unique<fake_netstack::internal::FakeNetwork>(&executor_); |
| }); |
| } |
| |
| FakeNetstack::~FakeNetstack() { |
| // Destruct the thread-hostile FakeNetwork instance on the dispatcher thread. |
| RunOnExecutorSync(executor_, [network = std::move(network_)]() mutable { network.reset(); }); |
| // Even once RunOnExecutorSync has completed the executor_ could still be running a task. This is |
| // because for us to know our 'task' completed, it gets wrapped in another task that signals us of |
| // completion. But now there is a chance of a race where we unblock and then finish this |
| // destructor before the executor_ finishes running the rest of the task and gets back to idle. |
| // Therefore we need to perform a graceful shutdown of the async loops thread to ensure there is |
| // definitely nothing running before shutting down the executor. |
| loop_.Quit(); |
| loop_.JoinThreads(); |
| } |
| |
| void FakeNetstack::Install(sys::testing::EnvironmentServices& services) { |
| zx_status_t status = |
| services.AddService(network_->GetHandler(), fuchsia::net::virtualization::Control::Name_); |
| FX_CHECK(status == ZX_OK) |
| << "Failure installing fuchsia.net.virtualization.Control into environment: " |
| << zx_status_get_string(status); |
| } |
| |
| fpromise::promise<void, zx_status_t> FakeNetstack::SendUdpPacket( |
| const fuchsia::hardware::ethernet::MacAddress& mac_addr, std::vector<uint8_t> packet) { |
| size_t total_length = sizeof(ethhdr) + sizeof(iphdr) + sizeof(udphdr) + packet.size(); |
| if (total_length > kMtu) { |
| return fpromise::make_error_promise(ZX_ERR_BUFFER_TOO_SMALL); |
| } |
| |
| std::vector<uint8_t> udp_packet; |
| udp_packet.reserve(total_length); |
| |
| { |
| ethhdr eth; |
| static_assert(sizeof(eth.h_dest) == sizeof(mac_addr.octets)); |
| memcpy(eth.h_dest, mac_addr.octets.data(), sizeof(eth.h_dest)); |
| static_assert(sizeof(eth.h_source) == sizeof(kHostMacAddress)); |
| memcpy(eth.h_source, kHostMacAddress, sizeof(eth.h_source)); |
| eth.h_proto = htons(kProtocolIpv4); |
| std::copy_n(reinterpret_cast<uint8_t*>(ð), sizeof(eth), std::back_inserter(udp_packet)); |
| } |
| |
| { |
| iphdr ip = { |
| .version = 4, |
| .tot_len = htons(static_cast<uint16_t>(sizeof(iphdr) + sizeof(udphdr) + packet.size())), |
| .ttl = UINT8_MAX, |
| .protocol = kPacketTypeUdp, |
| }; |
| ip.ihl = sizeof(iphdr) >> 2; // Header length in 32-bit words. |
| static_assert(sizeof(ip.saddr) == sizeof(kHostIpv4Address)); |
| memcpy(&ip.saddr, kHostIpv4Address, sizeof(ip.saddr)); |
| static_assert(sizeof(ip.daddr) == sizeof(kGuestIpv4Address)); |
| memcpy(&ip.daddr, kGuestIpv4Address, sizeof(ip.daddr)); |
| ip.check = Checksum(&ip, sizeof(iphdr), 0); |
| std::copy_n(reinterpret_cast<uint8_t*>(&ip), sizeof(ip), std::back_inserter(udp_packet)); |
| } |
| |
| { |
| udphdr udp = { |
| .source = htons(kTestPort), |
| .dest = htons(kTestPort), |
| .len = htons(static_cast<uint16_t>(sizeof(udphdr) + packet.size())), |
| }; |
| std::copy_n(reinterpret_cast<uint8_t*>(&udp), sizeof(udp), std::back_inserter(udp_packet)); |
| } |
| |
| std::copy(packet.begin(), packet.end(), std::back_inserter(udp_packet)); |
| |
| return SendPacket(mac_addr, std::move(udp_packet)); |
| } |
| |
| fpromise::promise<void, zx_status_t> FakeNetstack::SendPacket( |
| const fuchsia::hardware::ethernet::MacAddress& mac_addr, std::vector<uint8_t> packet) { |
| if (packet.size() > kMtu) { |
| return fpromise::make_error_promise(ZX_ERR_INVALID_ARGS); |
| } |
| |
| return fpromise::schedule_for_consumer( |
| &executor_, |
| fpromise::make_promise([mac_addr, this]() { return network_->GetDevice(mac_addr); }) |
| .and_then( |
| [packet = std::move(packet)](fake_netstack::internal::Device* const& device) { |
| return device->WritePacket(packet); |
| })) |
| .promise(); |
| } |
| |
| fpromise::promise<std::vector<uint8_t>, zx_status_t> FakeNetstack::ReceivePacket( |
| const fuchsia::hardware::ethernet::MacAddress& mac_addr) { |
| return fpromise::schedule_for_consumer( |
| &executor_, |
| fpromise::make_promise([mac_addr, this]() { return network_->GetDevice(mac_addr); }) |
| .and_then([](fake_netstack::internal::Device* const& device) { |
| return device->ReadPacket(); |
| }) |
| .and_then([](Packet& packet) -> fpromise::result<Packet, zx_status_t> { |
| return fpromise::ok(std::move(packet)); |
| })) |
| .promise(); |
| } |
| |
| void FakeNetstack::Start(std::unique_ptr<component_testing::LocalComponentHandles> handles) { |
| // This class contains handles to the component's incoming and outgoing capabilities. |
| handles_ = std::move(handles); |
| |
| ASSERT_EQ(handles_->outgoing()->AddPublicService(network_->GetHandler()), ZX_OK); |
| } |