// Copyright 2025 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.
#include "aemu/base/sockets/SocketUtils.h"

#include <errno.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <signal.h>
#include <string.h>

#include "aemu/base/sockets/ScopedSocket.h"

namespace android {
namespace base {

TEST(SocketUtils, socketSendAll) {
    char kData[] = "Hello World!";
    const size_t kDataLen = sizeof(kData) - 1U;

    int sock[2];
    ASSERT_EQ(0, socketCreatePair(&sock[0], &sock[1]));

    EXPECT_TRUE(socketSendAll(sock[0], kData, kDataLen));

    char data[kDataLen] = {};
    for (size_t n = 0; n < kDataLen; ++n) {
        EXPECT_EQ(1, socketRecv(sock[1], &data[n], 1)) << "#" << n;
        EXPECT_EQ(kData[n], data[n]) << "#" << n;
    }

    socketClose(sock[1]);
    socketClose(sock[0]);
}

TEST(SocketUtils, socketRecvAll) {
    char kData[] = "Hello World!";
    const size_t kDataLen = sizeof(kData) - 1U;

    int sock[2];
    ASSERT_EQ(0, socketCreatePair(&sock[0], &sock[1]));

    for (size_t n = 0; n < kDataLen; ++n) {
        EXPECT_EQ(1, socketSend(sock[0], &kData[n], 1)) << "#" << n;
    }

    char data[kDataLen] = {};
    EXPECT_TRUE(socketRecvAll(sock[1], data, kDataLen));
    EXPECT_THAT(data, ::testing::ElementsAreArray(kData, kDataLen));

    socketClose(sock[1]);
    socketClose(sock[0]);
}

TEST(SocketUtils, socketGetPort) {
    ScopedSocket s0;
    // Find a free TCP IPv4 port and bind to it.
    int port;
    for (port = 1024; port < 65536; ++port) {
        s0.reset(socketTcp4LoopbackServer(port));
        if (s0.valid()) {
            break;
        }
    }
    ASSERT_TRUE(s0.valid()) << "Could not find free TCP port";
    EXPECT_EQ(port, socketGetPort(s0.get()));

    // Connect to it.
    ScopedSocket s1(socketTcp4LoopbackClient(port));
    ASSERT_TRUE(s1.valid());

    // Accept it to create a pair of connected socket.
    // The port number of |s2| should be |port|.
    ScopedSocket s2(socketAcceptAny(s0.get()));
    ASSERT_TRUE(s2.valid());

    EXPECT_EQ(port, socketGetPort(s2.get()));
}

TEST(SocketUtils, socketGetPeerPort) {
    // Create a server socket bound to a random port.
    ScopedSocket s0(socketTcp4LoopbackServer(0));
    ASSERT_TRUE(s0.valid());
    int serverPort = socketGetPort(s0.get());
    ASSERT_GT(serverPort, 0);

    // Connect to it.
    ScopedSocket s1(socketTcp4LoopbackClient(serverPort));
    ASSERT_TRUE(s1.valid()) << "Could not connect to server socket";

    // Accept it to create a pair of connected sockets.
    ScopedSocket s2(socketAcceptAny(s0.get()));
    ASSERT_TRUE(s2.valid());

    EXPECT_EQ(socketGetPort(s1.get()), socketGetPeerPort(s2.get()));
    EXPECT_EQ(socketGetPort(s2.get()), socketGetPeerPort(s1.get()));
}

TEST(SocketUtils, socketFailedConnection) {
    // Attempt to connect to a list of ports that are highly unlikely to be
    // available. Just to be sure we'll attempt multiple different ports and
    // only fail if all of them are indicated as successful.
    bool atLeastOneFailed = false;
    for (uint16_t port = 1; port < 1024; ++port) {
        ScopedSocket s(socketTcp4LoopbackClient(port));
        if (!s.valid()) {
            atLeastOneFailed = true;
            break;
        }
    }
    ASSERT_TRUE(atLeastOneFailed);
}

#ifndef _WIN32
namespace {

// Helper class to temporary install a SIGPIPE signal handler that
// will simply store the signal number into a global variable.
class SigPipeSignalHandler {
  public:
    SigPipeSignalHandler() {
        sSignal = -1;
        struct sigaction act = {};
        act.sa_handler = myHandler;
        ::sigaction(SIGPIPE, &act, &mOldAction);
    }

    ~SigPipeSignalHandler() { ::sigaction(SIGPIPE, &mOldAction, nullptr); }

    int signaled() const { return sSignal; }

  private:
    struct sigaction mOldAction;

    static int sSignal;

    static void myHandler(int sig) { sSignal = sig; }
};

// static
int SigPipeSignalHandler::sSignal = 0;

}  // namespace

TEST(SocketUtils, socketSendDoesNotGenerateSigPipe) {
    // Check that writing to a broken pipe does not generate a SIGPIPE
    // signal on non-Windows platforms.

    // First, check that this is true for socket pairs.
    {
        SigPipeSignalHandler handler;
        int s1, s2;
        EXPECT_EQ(-1, handler.signaled());
        ASSERT_EQ(0, socketCreatePair(&s1, &s2));
        socketClose(s2);
        errno = 0;
        EXPECT_EQ(-1, socketSend(s1, "x", 1));
        EXPECT_EQ(EPIPE, errno);
        EXPECT_EQ(-1, handler.signaled());
        socketClose(s1);
    }

    // Second, check that this is true for random TCP sockets.
    {
        SigPipeSignalHandler handler;
        EXPECT_EQ(-1, handler.signaled());
        ScopedSocket s1(socketTcp4LoopbackServer(0));  // Bind to random port
        ASSERT_TRUE(s1.valid());

        int port = socketGetPort(s1.get());
        ScopedSocket s2(socketTcp4LoopbackClient(port));
        ASSERT_TRUE(s2.valid());

        ScopedSocket s3(socketAcceptAny(s1.get()));
        ASSERT_TRUE(s3.valid());

        // s2 and s3 are now connected. Close s2 immediately, then try to
        // send data through s3.
        socketShutdownReads(s2.get());
        s2.close();

        // The EPIPE might not happen on the first send due to
        // TCP packet buffering in the kernel. Perform multiple send()
        // in a loop to work-around this.
        errno = 0;
        const int kMaxSendCount = 100;
        int n = 0;
        while (n < kMaxSendCount) {
            int ret = socketSend(s3.get(), "xxxx", 4);
            if (ret < 0) {
#ifdef __APPLE__
                // On OS X, errno is sometimes EPROTOTYPE instead of EPIPE
                // when this happens.
                EXPECT_THAT(errno, ::testing::AnyOf(EPIPE, EPROTOTYPE))
                    << "Failed to send data: `" << strerror(errno) << "`, errno: " << errno;
#else
                EXPECT_EQ(EPIPE, errno) << strerror(errno);
#endif
                break;
            }
            n++;
        }
        // For the record, the value of |n| would typically be 2 on Linux,
        // and 9 or 10 on Darwin, when run on local workstations.
        EXPECT_LT(n, kMaxSendCount);

        EXPECT_EQ(-1, handler.signaled());
    }
}
#endif  // !_WIN32

}  // namespace base
}  // namespace android
