/*
 * Copyright (c) 2009 University of Washington
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#ifndef TAP_BRIDGE_H
#define TAP_BRIDGE_H

#include "ns3/address.h"
#include "ns3/callback.h"
#include "ns3/data-rate.h"
#include "ns3/event-id.h"
#include "ns3/fd-reader.h"
#include "ns3/mac48-address.h"
#include "ns3/net-device.h"
#include "ns3/node.h"
#include "ns3/nstime.h"
#include "ns3/packet.h"
#include "ns3/ptr.h"
#include "ns3/traced-callback.h"

#include <cstring>

namespace ns3
{

/**
 * @ingroup tap-bridge
 * Class to perform the actual reading from a socket
 */
class TapBridgeFdReader : public FdReader
{
  private:
    FdReader::Data DoRead() override;
};

class Node;

/**
 * @ingroup tap-bridge
 *
 * @brief A bridge to make it appear that a real host process is connected to
 * an ns-3 net device.
 *
 * The Tap Bridge lives in a kind of a gray world somewhere between a
 * Linux host and an ns-3 bridge device.  From the Linux perspective,
 * this code appears as the user mode handler for a Tap net device.  That
 * is, when the Linux host writes to a /dev/tap device (that is either
 * manually or automatically created depending on basic operating mode
 * -- more on this later), the write is redirected into the TapBridge that
 * lives in the ns-3 world; and from this perspective, becomes a read.
 * In other words, a Linux process writes a packet to a tap device and
 * this packet is redirected to an ns-3 process where it is received by
 * the TapBridge as a result of a read operation there.  The TapBridge
 * then sends the packet to the ns-3 net device to which it is bridged.
 * In the other direction, a packet received by an ns-3 net device is
 * bridged to the TapBridge (it appears via a callback from that net
 * device.  The TapBridge then takes that packet and writes it back to
 * the host using the Linux TAP mechanism.  This write to the device will
 * then appear to the Linux host as if a packet has arrived on its
 * device.
 *
 * The upshot is that the Tap Bridge appears to bridge a tap device on a
 * Linux host in the "real world" to an ns-3 net device in the simulation
 * and make is appear that a ns-3 net device is actually installed in the
 * Linux host.  In order to do this on the ns-3 side, we need a "ghost
 * node" in the simulation to hold the bridged ns-3 net device and the
 * TapBridge.  This node should not actually do anything else in the
 * simulation since its job is simply to make the net device appear in
 * Linux.  This is not just arbitrary policy, it is because:
 *
 * - Bits sent to the Tap Bridge from higher layers in the ghost node (using
 *   the TapBridge Send() method) are completely ignored.  The Tap Bridge is
 *   not, itself, connected to any network, neither in Linux nor in ns-3;
 * - The bridged ns-3 net device is has had its receive callback disconnected
 *   from the ns-3 node and reconnected to the Tap Bridge.  All data received
 *   by a bridged device will be sent to the Linux host and will not be
 *   received by the node.  From the perspective of the ghost node, you can
 *   send over this device but you cannot ever receive.
 *
 * Of course, if you understand all of the issues you can take control of
 * your own destiny and do whatever you want -- we do not actively
 * prevent you from using the ghost node for anything you decide.  You
 * will be able to perform typical ns-3 operations on the ghost node if
 * you so desire.  The internet stack, for example, must be there and
 * functional on that node in order to participate in IP address
 * assignment and global routing.  However, as mentioned above,
 * interfaces talking any Tap Bridge or associated bridged net devices
 * will not work completely.  If you understand exactly what you are
 * doing, you can set up other interfaces and devices on the ghost node
 * and use them; or take advantage of the operational send side of the
 * bridged devices to create traffic generators.  We generally recommend
 * that you treat this node as a ghost of the Linux host and leave it to
 * itself, though.
 */
class TapBridge : public NetDevice
{
  public:
    /**
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    /**
     * Enumeration of the operating modes supported in the class.
     *
     */
    enum Mode
    {
        ILLEGAL,         //!< mode not set
        CONFIGURE_LOCAL, //!< ns-3 creates and configures tap device
        USE_LOCAL,       //!< ns-3 uses a pre-created tap, without configuring it
        USE_BRIDGE,      //!< ns-3 uses a pre-created tap, and bridges to a bridging net device
    };

    TapBridge();
    ~TapBridge() override;

    /**
     * @brief Get the bridged net device.
     *
     * The bridged net device is the ns-3 device to which this bridge is connected,
     *
     * @returns the bridged net device.
     */
    Ptr<NetDevice> GetBridgedNetDevice();

    /**
     * @brief Set the ns-3 net device to bridge.
     *
     * This method tells the bridge which ns-3 net device it should use to connect
     * the simulation side of the bridge.
     *
     * @param bridgedDevice device to set
     *
     * @attention The ns-3 net device that is being set as the device must have an
     * an IP address assigned to it before the simulation is run.  This address
     * will be used to set the hardware address of the host Linux device.
     */
    void SetBridgedNetDevice(Ptr<NetDevice> bridgedDevice);

    /**
     * @brief Set a start time for the device.
     *
     * The tap bridge consumes a non-trivial amount of time to start.  It starts
     * up in the context of a scheduled event to ensure that all configuration
     * has been completed before extracting the configuration (IP addresses, etc.)
     * In order to allow a more reasonable start-up sequence than a thundering
     * herd of devices, the time at which each device starts is also configurable
     * bot via the Attribute system and via this call.
     *
     * @param tStart the start time
     */
    void Start(Time tStart);

    /**
     * Set a stop time for the device.
     *
     * @param tStop the stop time
     *
     * @see TapBridge::Start
     */
    void Stop(Time tStop);

    /**
     * Set the operating mode of this device.
     *
     * @param mode The operating mode of this device.
     */
    void SetMode(TapBridge::Mode mode);

    /**
     * Get the operating mode of this device.
     *
     * @returns The operating mode of this device.
     */
    TapBridge::Mode GetMode();

    //
    // The following methods are inherited from NetDevice base class and are
    // documented there.
    //
    void SetIfIndex(const uint32_t index) override;
    uint32_t GetIfIndex() const override;
    Ptr<Channel> GetChannel() const override;
    void SetAddress(Address address) override;
    Address GetAddress() const override;
    bool SetMtu(const uint16_t mtu) override;
    uint16_t GetMtu() const override;
    bool IsLinkUp() const override;
    void AddLinkChangeCallback(Callback<void> callback) override;
    bool IsBroadcast() const override;
    Address GetBroadcast() const override;
    bool IsMulticast() const override;
    Address GetMulticast(Ipv4Address multicastGroup) const override;
    bool IsPointToPoint() const override;
    bool IsBridge() const override;
    bool Send(Ptr<Packet> packet, const Address& dest, uint16_t protocolNumber) override;
    bool SendFrom(Ptr<Packet> packet,
                  const Address& source,
                  const Address& dest,
                  uint16_t protocolNumber) override;
    Ptr<Node> GetNode() const override;
    void SetNode(Ptr<Node> node) override;
    bool NeedsArp() const override;
    void SetReceiveCallback(NetDevice::ReceiveCallback cb) override;
    void SetPromiscReceiveCallback(NetDevice::PromiscReceiveCallback cb) override;
    bool SupportsSendFrom() const override;
    Address GetMulticast(Ipv6Address addr) const override;

  protected:
    /**
     * Call out to a separate process running as suid root in order to get our
     * tap device created.  We do this to avoid having the entire simulation
     * running as root.  If this method returns, we'll have a socket waiting
     * for us in m_sock that we can use to talk to the tap device.
     */
    void DoDispose() override;

    /**
     * Receives a packet from a bridged Device
     * @param device the originating port
     * @param packet the received packet
     * @param protocol the packet protocol (e.g., Ethertype)
     * @param src the packet source
     * @param dst the packet destination
     * @param packetType the packet type (e.g., host, broadcast, etc.)
     * @returns true on success
     */
    bool ReceiveFromBridgedDevice(Ptr<NetDevice> device,
                                  Ptr<const Packet> packet,
                                  uint16_t protocol,
                                  const Address& src,
                                  const Address& dst,
                                  PacketType packetType);

    /**
     * Receives a packet from a bridged Device
     * @param device the originating port
     * @param packet the received packet
     * @param protocol the packet protocol (e.g., Ethertype)
     * @param src the packet source
     * @returns true on success
     */
    bool DiscardFromBridgedDevice(Ptr<NetDevice> device,
                                  Ptr<const Packet> packet,
                                  uint16_t protocol,
                                  const Address& src);

  private:
    /**
     * Call out to a separate process running as suid root in order to get our
     * tap device created.  We do this to avoid having the entire simulation
     * running as root.  If this method returns, we'll have a socket waiting
     * for us in m_sock that we can use to talk to the tap device.
     */
    void CreateTap();

    /**
     * Spin up the device
     */
    void StartTapDevice();

    /**
     * Tear down the device
     */
    void StopTapDevice();

    /**
     * Callback to process packets that are read
     * @param buf input buffer
     * @param len input buffer length
     */
    void ReadCallback(uint8_t* buf, ssize_t len);

    /**
     * Forward a packet received from the tap device to the bridged ns-3
     * device
     *
     * @param buf A character buffer containing the actual packet bits that were
     *            received from the host.
     * @param len The length of the buffer.
     */
    void ForwardToBridgedDevice(uint8_t* buf, ssize_t len);

    /**
     * The host we are bridged to is in the evil real world.  Do some sanity
     * checking on a received packet to make sure it isn't too evil for our
     * poor naive virginal simulator to handle.
     *
     * @param packet The packet we received from the host, and which we need
     *               to check.
     * @param src    A pointer to the data structure that will get the source
     *               MAC address of the packet (extracted from the packet Ethernet
     *               header).
     * @param dst    A pointer to the data structure that will get the destination
     *               MAC address of the packet (extracted from the packet Ethernet
     *               header).
     * @param type   A pointer to the variable that will get the packet type from
     *               either the Ethernet header in the case of type interpretation
     *               (DIX framing) or from the 802.2 LLC header in the case of
     *               length interpretation (802.3 framing).
     * @returns the packet, or null if the packet has been filtered.
     */
    Ptr<Packet> Filter(Ptr<Packet> packet, Address* src, Address* dst, uint16_t* type);

    /**
     * Notifies that the link is up and ready.
     */
    void NotifyLinkUp();

    /**
     * Callback used to hook the standard packet receive callback of the TapBridge
     * ns-3 net device.  This is never called, and therefore no packets will ever
     * be received forwarded up the IP stack on the ghost node through this device.
     */
    NetDevice::ReceiveCallback m_rxCallback;

    /**
     * Callback used to hook the promiscuous packet receive callback of the TapBridge
     * ns-3 net device.  This is never called, and therefore no packets will ever
     * be received forwarded up the IP stack on the ghost node through this device.
     *
     * Note that we intercept the similar callback in the bridged device in order to
     * do the actual bridging between the bridged ns-3 net device and the Tap device
     * on the host.
     */
    NetDevice::PromiscReceiveCallback m_promiscRxCallback;

    /**
     * Pointer to the (ghost) Node to which we are connected.
     */
    Ptr<Node> m_node;

    /**
     * The ns-3 interface index of this TapBridge net device.
     */
    uint32_t m_ifIndex;

    /**
     * The common mtu to use for the net devices
     */
    uint16_t m_mtu;

    /**
     * The socket (actually interpreted as fd) to use to talk to the Tap device on
     * the real internet host.
     */
    int m_sock;

    /**
     * The ID of the ns-3 event used to schedule the start up of the underlying
     * host Tap device and ns-3 read thread.
     */
    EventId m_startEvent;

    /**
     * The ID of the ns-3 event used to schedule the tear down of the underlying
     * host Tap device and ns-3 read thread.
     */
    EventId m_stopEvent;

    /**
     * Includes the ns-3 read thread used to do blocking reads on the fd
     * corresponding to the host device.
     */
    Ptr<TapBridgeFdReader> m_fdReader;

    /**
     * The operating mode of the bridge.  Tells basically who creates and
     * configures the underlying network tap.
     */
    Mode m_mode;

    /**
     * The (unused) MAC address of the TapBridge net device.  Since the TapBridge
     * is implemented as a ns-3 net device, it is required to implement certain
     * functionality.  In this case, the TapBridge is automatically assigned a
     * MAC address, but it is not used.
     */
    Mac48Address m_address;

    /**
     * Time to start spinning up the device
     */
    Time m_tStart;

    /**
     * Time to start tearing down the device
     */
    Time m_tStop;

    /**
     * The name of the device to create on the host.  If the device name is the
     * empty string, we allow the host kernel to choose a name.
     */
    std::string m_tapDeviceName;

    /**
     * The IP address to use as the device default gateway on the host.
     */
    Ipv4Address m_tapGateway; // NS_DEPRECATED_3_44

    /**
     * The IP address to use as the device IP on the host.
     */
    Ipv4Address m_tapIp;
    /**
     * The MAC address to use as the hardware address on the host; only used
     * in UseLocal mode.  This value comes from the MAC
     * address assigned to the bridged ns-3 net device and matches the MAC
     * address of the underlying network TAP which we configured to have the
     * same value.
     */
    Mac48Address m_tapMac;

    /**
     * The network mask to assign to the device created on the host.
     */
    Ipv4Mask m_tapNetmask;

    /**
     * The ns-3 net device to which we are bridging.
     */
    Ptr<NetDevice> m_bridgedDevice;
    /**
     * Whether the MAC address of the underlying ns-3 device has already been
     * rewritten is stored in this variable (for UseLocal/UseBridge mode only).
     */
    bool m_ns3AddressRewritten;

    /**
     * A 64K buffer to hold packet data while it is being sent.
     */
    uint8_t* m_packetBuffer;

    /**
     * a copy of the node id so the read thread doesn't have to GetNode() in
     * in order to find the node ID.  Thread unsafe reference counting in
     * multithreaded apps is not a good thing.
     */
    uint32_t m_nodeId;

    /**
     * Flag indicating whether or not the link is up.  In this case,
     * whether or not ns-3 is connected to the underlying TAP device
     * with a file descriptor.
     */
    bool m_linkUp{false};

    /**
     * Flag indicating whether or not the link is up.  In this case,
     * whether or not ns-3 is connected to the underlying TAP device
     * with a file descriptor.
     */
    bool m_verbose;

    /**
     * Callbacks to fire if the link changes state (up or down).
     */
    TracedCallback<> m_linkChangeCallbacks;
};

} // namespace ns3

#endif /* TAP_BRIDGE_H */
