// Copyright 2018 The gVisor Authors.
//
// 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 <arpa/inet.h>
#include <netinet/in.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <unistd.h>

#include <iostream>
#include <vector>

#include "gtest/gtest.h"
#include "absl/strings/string_view.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"

namespace gvisor {
namespace testing {
namespace {

class SendFileTest : public ::testing::TestWithParam<int> {
 protected:
  PosixErrorOr<std::tuple<int, int>> Sockets() {
    // Bind a server socket.
    int family = GetParam();
    struct sockaddr server_addr = {};
    switch (family) {
      case AF_INET: {
        struct sockaddr_in* server_addr_in =
            reinterpret_cast<struct sockaddr_in*>(&server_addr);
        server_addr_in->sin_family = family;
        server_addr_in->sin_addr.s_addr = INADDR_ANY;
        break;
      }
      case AF_UNIX: {
        struct sockaddr_un* server_addr_un =
            reinterpret_cast<struct sockaddr_un*>(&server_addr);
        server_addr_un->sun_family = family;
        server_addr_un->sun_path[0] = '\0';
        break;
      }
      default:
        return PosixError(EINVAL);
    }
    int server = socket(family, SOCK_STREAM, 0);
    if (bind(server, &server_addr, sizeof(server_addr)) < 0) {
      return PosixError(errno);
    }
    if (listen(server, 1) < 0) {
      close(server);
      return PosixError(errno);
    }

    // Fetch the address; both are anonymous.
    socklen_t length = sizeof(server_addr);
    if (getsockname(server, &server_addr, &length) < 0) {
      close(server);
      return PosixError(errno);
    }

    // Connect the client.
    int client = socket(family, SOCK_STREAM, 0);
    if (connect(client, &server_addr, length) < 0) {
      close(server);
      close(client);
      return PosixError(errno);
    }

    // Accept on the server.
    int server_client = accept(server, nullptr, 0);
    if (server_client < 0) {
      close(server);
      close(client);
      return PosixError(errno);
    }
    close(server);
    return std::make_tuple(client, server_client);
  }
};

// Sends large file to exercise the path that read and writes data multiple
// times, esp. when more data is read than can be written.
TEST_P(SendFileTest, SendMultiple) {
  std::vector<char> data(5 * 1024 * 1024);
  RandomizeBuffer(data.data(), data.size());

  // Create temp files.
  const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
      GetAbsoluteTestTmpdir(), absl::string_view(data.data(), data.size()),
      TempPath::kDefaultFileMode));
  const TempPath out_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());

  // Create sockets.
  std::tuple<int, int> fds = ASSERT_NO_ERRNO_AND_VALUE(Sockets());
  const FileDescriptor server(std::get<0>(fds));
  FileDescriptor client(std::get<1>(fds));  // non-const, reset is used.

  // Thread that reads data from socket and dumps to a file.
  ScopedThread th([&] {
    FileDescriptor outf =
        ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_WRONLY));

    // Read until socket is closed.
    char buf[10240];
    for (int cnt = 0;; cnt++) {
      int r = RetryEINTR(read)(server.get(), buf, sizeof(buf));
      // We cannot afford to save on every read() call.
      if (cnt % 1000 == 0) {
        ASSERT_THAT(r, SyscallSucceeds());
      } else {
        const DisableSave ds;
        ASSERT_THAT(r, SyscallSucceeds());
      }
      if (r == 0) {
        // EOF
        break;
      }
      int w = RetryEINTR(write)(outf.get(), buf, r);
      // We cannot afford to save on every write() call.
      if (cnt % 1010 == 0) {
        ASSERT_THAT(w, SyscallSucceedsWithValue(r));
      } else {
        const DisableSave ds;
        ASSERT_THAT(w, SyscallSucceedsWithValue(r));
      }
    }
  });

  // Open the input file as read only.
  const FileDescriptor inf =
      ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));

  int cnt = 0;
  for (size_t sent = 0; sent < data.size(); cnt++) {
    const size_t remain = data.size() - sent;
    std::cout << "sendfile, size=" << data.size() << ", sent=" << sent
              << ", remain=" << remain;

    // Send data and verify that sendfile returns the correct value.
    int res = sendfile(client.get(), inf.get(), nullptr, remain);
    // We cannot afford to save on every sendfile() call.
    if (cnt % 120 == 0) {
      MaybeSave();
    }
    if (res == 0) {
      // EOF
      break;
    }
    if (res > 0) {
      sent += res;
    } else {
      ASSERT_TRUE(errno == EINTR || errno == EAGAIN) << "errno=" << errno;
    }
  }

  // Close socket to stop thread.
  client.reset();
  th.Join();

  // Verify that the output file has the correct data.
  const FileDescriptor outf =
      ASSERT_NO_ERRNO_AND_VALUE(Open(out_file.path(), O_RDONLY));
  std::vector<char> actual(data.size(), '\0');
  ASSERT_THAT(RetryEINTR(read)(outf.get(), actual.data(), actual.size()),
              SyscallSucceedsWithValue(actual.size()));
  ASSERT_EQ(memcmp(data.data(), actual.data(), data.size()), 0);
}

TEST_P(SendFileTest, Shutdown) {
  // Create a socket.
  std::tuple<int, int> fds = ASSERT_NO_ERRNO_AND_VALUE(Sockets());
  const FileDescriptor client(std::get<0>(fds));
  FileDescriptor server(std::get<1>(fds));  // non-const, reset below.

  // If this is a TCP socket, then turn off linger.
  if (GetParam() == AF_INET) {
    struct linger sl;
    sl.l_onoff = 1;
    sl.l_linger = 0;
    ASSERT_THAT(
        setsockopt(server.get(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)),
        SyscallSucceeds());
  }

  // Create a 1m file with random data.
  std::vector<char> data(1024 * 1024);
  RandomizeBuffer(data.data(), data.size());
  const TempPath in_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
      GetAbsoluteTestTmpdir(), absl::string_view(data.data(), data.size()),
      TempPath::kDefaultFileMode));
  const FileDescriptor inf =
      ASSERT_NO_ERRNO_AND_VALUE(Open(in_file.path(), O_RDONLY));

  // Read some data, then shutdown the socket. We don't actually care about
  // checking the contents (other tests do that), so we just re-use the same
  // buffer as above.
  ScopedThread t([&]() {
    size_t done = 0;
    while (done < data.size()) {
      int n = RetryEINTR(read)(server.get(), data.data(), data.size());
      ASSERT_THAT(n, SyscallSucceeds());
      done += n;
    }
    // Close the server side socket.
    server.reset();
  });

  // Continuously stream from the file to the socket. Note we do not assert
  // that a specific amount of data has been written at any time, just that some
  // data is written. Eventually, we should get a connection reset error.
  while (1) {
    off_t offset = 0;  // Always read from the start.
    int n = sendfile(client.get(), inf.get(), &offset, data.size());
    EXPECT_THAT(n, AnyOf(SyscallFailsWithErrno(ECONNRESET),
                         SyscallFailsWithErrno(EPIPE), SyscallSucceeds()));
    if (n <= 0) {
      break;
    }
  }
}

INSTANTIATE_TEST_SUITE_P(AddressFamily, SendFileTest,
                         ::testing::Values(AF_UNIX, AF_INET));

}  // namespace
}  // namespace testing
}  // namespace gvisor