Additional low-level socket API functionality

Needed a bit more functionality to support the higher-level TCP socket API coming in a subsequent CL.

PiperOrigin-RevId: 458307839
diff --git a/quiche/quic/core/io/socket.h b/quiche/quic/core/io/socket.h
index 6750af7..748de4d 100644
--- a/quiche/quic/core/io/socket.h
+++ b/quiche/quic/core/io/socket.h
@@ -9,6 +9,7 @@
 #include "absl/status/statusor.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/platform/api/quic_ip_address_family.h"
 #include "quiche/quic/platform/api/quic_socket_address.h"
 #include "quiche/common/platform/api/quiche_export.h"
@@ -53,11 +54,23 @@
 // `blocking` false). Must be a change from previous state.
 absl::Status SetSocketBlocking(SocketFd fd, bool blocking);
 
+// Sets buffer sizes for socket `fd` to `size` bytes.
+absl::Status SetReceiveBufferSize(SocketFd fd, QuicByteCount size);
+absl::Status SetSendBufferSize(SocketFd fd, QuicByteCount size);
+
 // Connects socket `fd` to `peer_address`.  Returns a status with
 // `absl::StatusCode::kUnavailable` iff the socket is non-blocking and the
-// connection could not be immediately completed.
+// connection could not be immediately completed.  The socket will then complete
+// connecting asynchronously, and on becoming writable, the result can be
+// checked using GetSocketError().
 absl::Status Connect(SocketFd fd, const QuicSocketAddress& peer_address);
 
+// Gets and clears socket error information for socket `fd`. Note that returned
+// error could be either the found socket error, or unusually, an error from the
+// attempt to retrieve error information. Typically used to determine connection
+// result after asynchronous completion of a Connect() call.
+absl::Status GetSocketError(SocketFd fd);
+
 // Assign `address` to socket `fd`.
 absl::Status Bind(SocketFd fd, const QuicSocketAddress& address);
 
@@ -83,8 +96,10 @@
 // `buffer.size()` bytes. On success, returns a span pointing to the buffer
 // but resized to the actual number of bytes received. Returns a status with
 // `absl::StatusCode::kUnavailable` iff the socket is non-blocking and the
-// receive operation could not be immediately completed.
-absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer);
+// receive operation could not be immediately completed.  If `peek` is true,
+// received data is not removed from the underlying socket data queue.
+absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer,
+                                         bool peek = false);
 
 // Sends some or all of the data in `buffer` to socket `fd`. On success,
 // returns a string_view pointing to the unsent remainder of the buffer (or an
diff --git a/quiche/quic/core/io/socket_posix.cc b/quiche/quic/core/io/socket_posix.cc
index 5a89c69..59f286b 100644
--- a/quiche/quic/core/io/socket_posix.cc
+++ b/quiche/quic/core/io/socket_posix.cc
@@ -4,6 +4,8 @@
 #include <fcntl.h>
 #include <sys/socket.h>
 
+#include <climits>
+
 #include "absl/base/attributes.h"
 #include "absl/container/flat_hash_set.h"
 #include "absl/status/status.h"
@@ -212,6 +214,24 @@
   }
 }
 
+absl::Status SetSockOptInt(SocketFd fd, int option, int value) {
+  QUICHE_DCHECK_GE(fd, 0);
+
+  int result;
+  do {
+    result = ::setsockopt(fd, SOL_SOCKET, option, &value, sizeof(value));
+  } while (result < 0 && errno == EINTR);
+
+  if (result >= 0) {
+    return absl::OkStatus();
+  } else {
+    absl::Status status = ToStatus(errno, "::setsockopt()");
+    QUICHE_DVLOG(1) << "Failed to set socket " << fd << " option " << option
+                    << " to " << value << " with error: " << status;
+    return status;
+  }
+}
+
 }  // namespace
 
 absl::StatusOr<SocketFd> CreateSocket(IpAddressFamily address_family,
@@ -257,6 +277,20 @@
   }
 }
 
+absl::Status SetReceiveBufferSize(SocketFd fd, QuicByteCount size) {
+  QUICHE_DCHECK_GE(fd, 0);
+  QUICHE_DCHECK_LE(size, QuicByteCount{INT_MAX});
+
+  return SetSockOptInt(fd, SO_RCVBUF, static_cast<int>(size));
+}
+
+absl::Status SetSendBufferSize(SocketFd fd, QuicByteCount size) {
+  QUICHE_DCHECK_GE(fd, 0);
+  QUICHE_DCHECK_LE(size, QuicByteCount{INT_MAX});
+
+  return SetSockOptInt(fd, SO_SNDBUF, static_cast<int>(size));
+}
+
 absl::Status Connect(SocketFd fd, const QuicSocketAddress& peer_address) {
   QUICHE_DCHECK_GE(fd, 0);
   QUICHE_DCHECK(peer_address.IsInitialized());
@@ -283,6 +317,32 @@
   }
 }
 
+absl::Status GetSocketError(SocketFd fd) {
+  QUICHE_DCHECK_GE(fd, 0);
+
+  int socket_error = 0;
+  socklen_t len = sizeof(socket_error);
+  int sockopt_result;
+  do {
+    sockopt_result =
+        ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, &len);
+  } while (sockopt_result < 0 && errno == EINTR);
+
+  if (sockopt_result >= 0) {
+    if (socket_error == 0) {
+      return absl::OkStatus();
+    } else {
+      return ToStatus(socket_error, "SO_ERROR");
+    }
+  } else {
+    absl::Status status = ToStatus(errno, "::getsockopt()");
+    QUICHE_LOG_FIRST_N(ERROR, 100)
+        << "Failed to get socket error information from socket " << fd
+        << " with error: " << status;
+    return status;
+  }
+}
+
 absl::Status Bind(SocketFd fd, const QuicSocketAddress& address) {
   QUICHE_DCHECK_GE(fd, 0);
   QUICHE_DCHECK(address.IsInitialized());
@@ -380,13 +440,15 @@
   return accept_result;
 }
 
-absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer) {
+absl::StatusOr<absl::Span<char>> Receive(SocketFd fd, absl::Span<char> buffer,
+                                         bool peek) {
   QUICHE_DCHECK_GE(fd, 0);
   QUICHE_DCHECK(!buffer.empty());
 
   ssize_t num_read;
   do {
-    num_read = ::recv(fd, buffer.data(), buffer.size(), /*flags=*/0);
+    num_read =
+        ::recv(fd, buffer.data(), buffer.size(), /*flags=*/peek ? MSG_PEEK : 0);
   } while (num_read < 0 && errno == EINTR);
 
   if (num_read > 0 && static_cast<size_t>(num_read) > buffer.size()) {
diff --git a/quiche/quic/core/io/socket_test.cc b/quiche/quic/core/io/socket_test.cc
index ecd611d..f738174 100644
--- a/quiche/quic/core/io/socket_test.cc
+++ b/quiche/quic/core/io/socket_test.cc
@@ -56,6 +56,24 @@
   EXPECT_TRUE(socket_api::Close(socket).ok());
 }
 
+TEST(SocketTest, SetReceiveBufferSize) {
+  SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp,
+                                     /*blocking=*/true);
+
+  EXPECT_TRUE(socket_api::SetReceiveBufferSize(socket, /*size=*/100).ok());
+
+  EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
+TEST(SocketTest, SetSendBufferSize) {
+  SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp,
+                                     /*blocking=*/true);
+
+  EXPECT_TRUE(socket_api::SetSendBufferSize(socket, /*size=*/100).ok());
+
+  EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
 TEST(SocketTest, Connect) {
   SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp);
 
@@ -67,6 +85,16 @@
   EXPECT_TRUE(socket_api::Close(socket).ok());
 }
 
+TEST(SocketTest, GetSocketError) {
+  SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp,
+                                     /*blocking=*/true);
+
+  absl::Status error = socket_api::GetSocketError(socket);
+  EXPECT_TRUE(error.ok());
+
+  EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
 TEST(SocketTest, Bind) {
   SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp);
 
@@ -134,6 +162,20 @@
   EXPECT_TRUE(socket_api::Close(socket).ok());
 }
 
+TEST(SocketTest, Peek) {
+  // Non-blocking to avoid waiting when no data to receive.
+  SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp,
+                                     /*blocking=*/false);
+
+  std::string buffer(100, 0);
+  absl::StatusOr<absl::Span<char>> result =
+      socket_api::Receive(socket, absl::MakeSpan(buffer), /*peek=*/true);
+  ASSERT_FALSE(result.ok());
+  EXPECT_TRUE(absl::IsUnavailable(result.status()));
+
+  EXPECT_TRUE(socket_api::Close(socket).ok());
+}
+
 TEST(SocketTest, Send) {
   SocketFd socket = CreateTestSocket(socket_api::SocketProtocol::kUdp);
   // UDP, so "connecting" should succeed without any listening sockets.