/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <android-base/macros.h>
#include <android-base/unique_fd.h>
#include <libnl++/Buffer.h>
#include <libnl++/Message.h>
#include <libnl++/MessageFactory.h>

#include <linux/netlink.h>
#include <poll.h>

#include <optional>
#include <set>
#include <vector>

namespace android::nl {

/**
 * A wrapper around AF_NETLINK sockets.
 *
 * This class is not thread safe to use a single instance between multiple threads, but it's fine to
 * use multiple instances over multiple threads.
 */
class Socket {
  public:
    static constexpr size_t defaultReceiveSize = 8192;

    /**
     * Socket constructor.
     *
     * \param protocol the Netlink protocol to use.
     * \param pid port id. Default value of 0 allows the kernel to assign us a unique pid.
     *        (NOTE: this is NOT the same as process id).
     * \param groups Netlink multicast groups to listen to. This is a 32-bit bitfield, where each
     *        bit is a different group. Default value of 0 means no groups are selected.
     *        See man netlink.7.
     * for more details.
     */
    Socket(int protocol, unsigned pid = 0, uint32_t groups = 0);

    /**
     * Attempt to clear POLLERR by recv-ing.
     * TODO(224850481): determine if this is necessary, or if the socket is locked up anyway.
     */
    void clearPollErr();

    /**
     * Send Netlink message with incremented sequence number to the Kernel.
     *
     * \param msg Message to send. Its sequence number will be updated.
     * \return true, if succeeded.
     */
    template <typename T, unsigned BUFSIZE>
    bool send(MessageFactory<T, BUFSIZE>& req) {
        sockaddr_nl sa = {};
        sa.nl_family = AF_NETLINK;
        sa.nl_pid = 0;  // Kernel
        return send(req, sa);
    }

    /**
     * Send Netlink message with incremented sequence number.
     *
     * \param msg Message to send. Its sequence number will be updated.
     * \param sa Destination address.
     * \return true, if succeeded.
     */
    template <typename T, unsigned BUFSIZE>
    bool send(MessageFactory<T, BUFSIZE>& req, const sockaddr_nl& sa) {
        req.header.nlmsg_seq = mSeq + 1;

        const auto msg = req.build();
        if (!msg.has_value()) return false;

        return send(*msg, sa);
    }

    /**
     * Send Netlink message.
     *
     * \param msg Message to send.
     * \param sa Destination address.
     * \return true, if succeeded.
     */
    bool send(const Buffer<nlmsghdr>& msg, const sockaddr_nl& sa);

    /**
     * Send Netlink message.
     *
     * \param msg Message to send.
     * \param destination Destination PID.
     * \return true, if succeeded.
     */
    bool send(const Buffer<nlmsghdr>& msg, uint32_t destination);

    /**
     * Receive one or multiple Netlink messages.
     *
     * WARNING: the underlying buffer is owned by Socket class and the data is valid until the next
     * call to the read function or until deallocation of Socket instance.
     *
     * \param maxSize Maximum total size of received messages
     * \return Buffer view with message data, std::nullopt on error.
     */
    std::optional<Buffer<nlmsghdr>> receive(size_t maxSize = defaultReceiveSize);

    /**
     * Receive one or multiple Netlink messages and the sender process address.
     *
     * WARNING: the underlying buffer is owned by Socket class and the data is valid until the next
     * call to the read function or until deallocation of Socket instance.
     *
     * \param maxSize Maximum total size of received messages.
     * \return A pair (for use with structured binding) containing:
     *         - buffer view with message data, std::nullopt on error;
     *         - sender process address.
     */
    std::pair<std::optional<Buffer<nlmsghdr>>, sockaddr_nl> receiveFrom(
            size_t maxSize = defaultReceiveSize);

    /**
     * Receive matching Netlink message of a given payload type.
     *
     * This method should be used if the caller expects exactly one incoming message of exactly
     * given type (such as ACK). If there is a use case to handle multiple types of messages,
     * please use receive(size_t) directly and iterate through potential multipart messages.
     *
     * If this method is used in such an environment, it will only return the first matching message
     * from multipart packet and will issue warnings on messages that do not match.
     *
     * \param msgtypes Expected message types (such as NLMSG_ERROR).
     * \param maxSize Maximum total size of received messages.
     * \return Parsed message or std::nullopt in case of error.
     */
    template <typename T>
    std::optional<Message<T>> receive(const std::set<nlmsgtype_t>& msgtypes,
                                      size_t maxSize = defaultReceiveSize) {
        const auto msg = receive(msgtypes, maxSize);
        if (!msg.has_value()) return std::nullopt;

        const auto parsed = Message<T>::parse(*msg);
        if (!parsed.has_value()) {
            LOG(WARNING) << "Received matching Netlink message, but couldn't parse it";
            return std::nullopt;
        }

        return parsed;
    }

    /**
     * Receive Netlink ACK message.
     *
     * \param req Message to match sequence number against.
     * \return true if received ACK message, false in case of error.
     */
    template <typename T, unsigned BUFSIZE>
    bool receiveAck(MessageFactory<T, BUFSIZE>& req) {
        return receiveAck(req.header.nlmsg_seq);
    }

    /**
     * Receive Netlink ACK message.
     *
     * \param seq Sequence number of message to ACK.
     * \return true if received ACK message, false in case of error.
     */
    bool receiveAck(uint32_t seq);

    /**
     * Fetches the socket PID.
     *
     * \return PID that socket is bound to or std::nullopt.
     */
    std::optional<unsigned> getPid();

    /**
     * Creates a pollfd object for the socket.
     *
     * \param events Value for pollfd.events.
     * \return A populated pollfd object.
     */
    pollfd preparePoll(short events = 0);

    /**
     * Join a multicast group.
     *
     * \param group Group ID (*not* a bitfield)
     * \return whether the operation succeeded
     */
    bool addMembership(unsigned group);

    /**
     * Leave a multicast group.
     *
     * \param group Group ID (*not* a bitfield)
     * \return whether the operation succeeded
     */
    bool dropMembership(unsigned group);

    /**
     * Live iterator continuously receiving messages from Netlink socket.
     *
     * Iteration ends when socket fails to receive a buffer.
     *
     * Example:
     * ```
     *     nl::Socket sock(NETLINK_ROUTE, 0, RTMGRP_LINK);
     *     for (const auto rawMsg : sock) {
     *         const auto msg = nl::Message<ifinfomsg>::parse(rawMsg, {RTM_NEWLINK, RTM_DELLINK});
     *         if (!msg.has_value()) continue;
     *
     *         LOG(INFO) << msg->attributes.get<std::string>(IFLA_IFNAME)
     *                   << " is " << ((msg->data.ifi_flags & IFF_UP) ? "up" : "down");
     *     }
     *     LOG(FATAL) << "Failed to read from Netlink socket";
     * ```
     */
    class receive_iterator {
      public:
        receive_iterator(Socket& socket, bool end);

        receive_iterator operator++();
        bool operator==(const receive_iterator& other) const;
        const Buffer<nlmsghdr>& operator*() const;

      private:
        Socket& mSocket;
        bool mIsEnd;
        Buffer<nlmsghdr>::iterator mCurrent;

        void receive();
    };
    receive_iterator begin();
    receive_iterator end();

  private:
    const int mProtocol;
    base::unique_fd mFd;
    std::vector<uint8_t> mReceiveBuffer;

    bool mFailed = false;
    uint32_t mSeq = 0;

    bool increaseReceiveBuffer(size_t maxSize);
    std::optional<Buffer<nlmsghdr>> receive(const std::set<nlmsgtype_t>& msgtypes, size_t maxSize);

    DISALLOW_COPY_AND_ASSIGN(Socket);
};

}  // namespace android::nl
