diff --git a/internal/platform/implementation/windows/BUILD b/internal/platform/implementation/windows/BUILD index d30fb177..bc41a682 100644 --- a/internal/platform/implementation/windows/BUILD +++ b/internal/platform/implementation/windows/BUILD @@ -93,6 +93,8 @@ cc_library( "file_path.h", "http_loader.h", "mutex.h", + "nearby_client_socket.h", + "nearby_server_socket.h", "scheduled_executor.h", "server_sync.h", "session_manager.h", @@ -185,6 +187,8 @@ cc_library( "file.cc", "file_path.cc", "http_loader.cc", + "nearby_client_socket.cc", + "nearby_server_socket.cc", "platform.cc", "preferences_manager.cc", "preferences_repository.cc", diff --git a/internal/platform/implementation/windows/nearby_client_socket.cc b/internal/platform/implementation/windows/nearby_client_socket.cc new file mode 100644 index 00000000..c640542b --- /dev/null +++ b/internal/platform/implementation/windows/nearby_client_socket.cc @@ -0,0 +1,206 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/windows/nearby_client_socket.h" + +#include +#include + +#include +#include +#include +#include + +#include "internal/flags/nearby_flags.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/flags/nearby_platform_feature_flags.h" +#include "internal/platform/logging.h" + +namespace nearby::windows { + +NearbyClientSocket::NearbyClientSocket() { + WSADATA wsa_data; + int result = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (result != 0) { + LOG(WARNING) << "WSAStartup failed with error " << result; + } + + is_socket_initiated_ = (result == 0); +} + +NearbyClientSocket::NearbyClientSocket(SOCKET socket) : socket_(socket) {} + +NearbyClientSocket::~NearbyClientSocket() { + if (is_socket_initiated_) { + WSACleanup(); + } +} + +bool NearbyClientSocket ::Connect(const std::string& ip_address, int port) { + if (!is_socket_initiated_) { + LOG(WARNING) << "Windows socket is not initiated."; + return false; + } + + if (socket_ != INVALID_SOCKET) { + LOG(ERROR) << "Socket is already connected."; + return false; + } + + socket_ = + socket(/*af=*/AF_INET, /*type=*/SOCK_STREAM, /*protocol=*/IPPROTO_TCP); + + if (socket_ == INVALID_SOCKET) { + LOG(ERROR) << "Failed to get socket with error " << WSAGetLastError(); + return false; + } + + struct sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port); + serv_addr.sin_addr.s_addr = inet_addr(ip_address.c_str()); + + BOOL flag = TRUE; + if (setsockopt(/*s=*/socket_, /*level=*/SOL_SOCKET, /*optname=*/SO_KEEPALIVE, + /*optval=*/(const char*)&flag, + /*optlen=*/sizeof(flag)) == SOCKET_ERROR) { + LOG(WARNING) << "Failed to set SO_KEEPALIVE with error " + << WSAGetLastError(); + } + + int send_buffer_size = + static_cast(NearbyFlags::GetInstance().GetInt64Flag( + nearby::platform::config_package_nearby::nearby_platform_feature:: + kSocketSendBufferSize)); + if (setsockopt(/*s=*/socket_, /*level=*/SOL_SOCKET, /*optname=*/SO_SNDBUF, + /*optval=*/(char*)&send_buffer_size, + /*optlen=*/sizeof(send_buffer_size)) == SOCKET_ERROR) { + LOG(WARNING) << "Failed to set SO_SNDBUF with error " << WSAGetLastError(); + } + + flag = TRUE; + setsockopt(/*s=*/socket_, /*level=*/IPPROTO_TCP, /*optname=*/TCP_NODELAY, + /*optval=*/(char*)&flag, /*optlen=*/sizeof(flag)); + + if (connect(/*s=*/socket_, /*name=*/(struct sockaddr*)&serv_addr, + /*namelen=*/sizeof(serv_addr)) == SOCKET_ERROR) { + LOG(ERROR) << "Failed to connect socket with error: " << WSAGetLastError(); + closesocket(socket_); + socket_ = INVALID_SOCKET; + return false; + } + + sockaddr_in local_address; + int address_length = sizeof(local_address); + if (getsockname(/*s=*/socket_, /*name=*/(SOCKADDR*)&local_address, + /*namelen=*/&address_length) != SOCKET_ERROR) { + // Extract the IP address and port + char local_ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(local_address.sin_addr), local_ip, INET_ADDRSTRLEN); + int local_port = ntohs(local_address.sin_port); + + LOG(INFO) << "Connected to " << ip_address << ":" << port << " from " + << local_ip << ":" << local_port; + } + + return true; +} + +ExceptionOr NearbyClientSocket::Read(std::int64_t size) { + if (socket_ == INVALID_SOCKET) { + LOG(WARNING) << "Trying to read on an invalid socket."; + return {Exception::kIo}; + } + + std::string buffer; + int count = 0; + + buffer.resize(size); + while (count < size) { + int result = + recv(/*s=*/socket_, /*buf=*/const_cast(buffer.data()) + count, + /*len=*/size - count, /*flags=*/MSG_WAITALL); + if (result == SOCKET_ERROR) { + LOG(ERROR) << "Failed to receive data " << WSAGetLastError(); + return {Exception::kIo}; + } + + if (result == 0) { + LOG(INFO) << "Socket closed gracefully."; + return {Exception::kIo}; + } + + count += result; + } + + return ExceptionOr(ByteArray(std::move(buffer))); +} + +ExceptionOr NearbyClientSocket::Skip(size_t offset) { + if (socket_ == INVALID_SOCKET) { + LOG(WARNING) << "Trying to skip to an invalid socket."; + return {Exception::kIo}; + } + + ExceptionOr data = Read(offset); + if (data.ok()) { + return ExceptionOr(data.result().size()); + } + + LOG(ERROR) << "Failed to skip data " << WSAGetLastError(); + return {Exception::kIo}; +} + +Exception NearbyClientSocket::Write(const ByteArray& data) { + if (socket_ == INVALID_SOCKET) { + LOG(WARNING) << "Trying to write to an invalid socket."; + return {Exception::kIo}; + } + + int count = 0; + while (count < data.size()) { + int result = send(/*s=*/socket_, /*buf=*/data.data() + count, + /*len=*/data.size() - count, /*flags*/ 0); + if (result == SOCKET_ERROR) { + LOG(ERROR) << "Failed to send data " << WSAGetLastError(); + return {Exception::kIo}; + } + count += result; + } + + return {Exception::kSuccess}; +} + +Exception NearbyClientSocket::Flush() { + // Socket doesn't support flush. + return {Exception::kSuccess}; +} + +Exception NearbyClientSocket::Close() { + if (socket_ == INVALID_SOCKET) { + LOG(WARNING) << "Trying to close an invalid socket."; + return {Exception::kIo}; + } + + shutdown(socket_, SD_BOTH); + closesocket(socket_); + socket_ = INVALID_SOCKET; + + LOG(INFO) << "Socket closed."; + + return {Exception::kSuccess}; +} + +} // namespace nearby::windows diff --git a/internal/platform/implementation/windows/nearby_client_socket.h b/internal/platform/implementation/windows/nearby_client_socket.h new file mode 100644 index 00000000..38d2ae42 --- /dev/null +++ b/internal/platform/implementation/windows/nearby_client_socket.h @@ -0,0 +1,49 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NEARBY_CLIENT_SOCKET_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NEARBY_CLIENT_SOCKET_H_ + +#include + +#include +#include +#include + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" + +namespace nearby::windows { + +class NearbyClientSocket { + public: + NearbyClientSocket(); + explicit NearbyClientSocket(SOCKET socket); + ~NearbyClientSocket(); + + bool Connect(const std::string& ip_address, int port); + ExceptionOr Read(std::int64_t size); + ExceptionOr Skip(size_t offset); + Exception Write(const ByteArray& data); + Exception Flush(); + Exception Close(); + + private: + bool is_socket_initiated_ = false; + SOCKET socket_ = INVALID_SOCKET; +}; + +} // namespace nearby::windows + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NEARBY_CLIENT_SOCKET_H_ diff --git a/internal/platform/implementation/windows/nearby_server_socket.cc b/internal/platform/implementation/windows/nearby_server_socket.cc new file mode 100644 index 00000000..217886d5 --- /dev/null +++ b/internal/platform/implementation/windows/nearby_server_socket.cc @@ -0,0 +1,147 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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 "internal/platform/implementation/windows/nearby_server_socket.h" + +#include +#include + +#include +#include + +#include "internal/platform/implementation/windows/nearby_client_socket.h" +#include "internal/platform/logging.h" + +namespace nearby::windows { + +NearbyServerSocket::NearbyServerSocket() { + WSADATA wsa_data; + int result = + WSAStartup(/*wVersionRequired=*/MAKEWORD(2, 2), /*lpWSAData=*/&wsa_data); + if (result != 0) { + LOG(WARNING) << "WSAStartup failed with error:" << result; + } + + is_socket_initiated_ = (result == 0); +} + +NearbyServerSocket::~NearbyServerSocket() { + if (is_socket_initiated_) { + WSACleanup(); + } +} + +bool NearbyServerSocket::Listen(const std::string& ip_address, int port) { + LOG(INFO) << "Listen to socket at " << ip_address << ":" << port; + if (!is_socket_initiated_) { + LOG(ERROR) << "Windows socket is not initiated."; + return false; + } + + socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket_ == INVALID_SOCKET) { + LOG(ERROR) << "Failed to create socket."; + return false; + } + + BOOL flag = TRUE; + if (setsockopt(/*s=*/socket_, /*level=*/SOL_SOCKET, /*optname=*/SO_KEEPALIVE, + /*optval=*/(const char*)&flag, + /*optlen=*/sizeof(flag)) == SOCKET_ERROR) { + LOG(WARNING) << "Failed to set SO_KEEPALIVE with error " + << WSAGetLastError(); + } + + struct sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port); + serv_addr.sin_addr.s_addr = inet_addr(ip_address.c_str()); + + if (bind(/*s=*/socket_, /*addr=*/(struct sockaddr*)&serv_addr, + /*namelen=*/sizeof(serv_addr)) == SOCKET_ERROR) { + LOG(ERROR) << "Failed to bind socket with error " << WSAGetLastError(); + closesocket(socket_); + return false; + } + + sockaddr_in local_address; + int address_length = sizeof(local_address); + if (getsockname(/*s=*/socket_, (/*name=*/SOCKADDR*)&local_address, + /*namelen=*/&address_length) == SOCKET_ERROR) { + LOG(ERROR) << "Failed to get socket name with error " << WSAGetLastError(); + closesocket(socket_); + return false; + } + + ip_address_ = ip_address; + port_ = ntohs(local_address.sin_port); + + LOG(INFO) << "Bound to " << ip_address_ << ":" << port_; + + if (::listen(/*s=*/socket_, /*backlog=*/SOMAXCONN) == SOCKET_ERROR) { + LOG(ERROR) << "Failed to listen socket with error " << WSAGetLastError(); + closesocket(socket_); + return false; + } + + return true; +} + +std::unique_ptr NearbyServerSocket::Accept() { + LOG(INFO) << "Accept is called on NearbyServerSocket."; + if (!is_socket_initiated_) { + LOG(WARNING) << "Windows socket is not initiated"; + return nullptr; + } + + sockaddr_in peer_address; + int peer_address_length = sizeof(peer_address); + + SOCKET client_socket = + accept(/*s=*/socket_, /*addr=*/(SOCKADDR*)&peer_address, + /*addrlen=*/&peer_address_length); + if (client_socket == INVALID_SOCKET) { + LOG(ERROR) << "Failed to accept socket with error: " << WSAGetLastError(); + return nullptr; + } + + char client_ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(peer_address.sin_addr), client_ip, INET_ADDRSTRLEN); + int client_port = ntohs(peer_address.sin_port); + + LOG(INFO) << "Accepted emote device " << client_ip << ":" << client_port; + return std::make_unique(client_socket); +} + +bool NearbyServerSocket::Close() { + bool result = true; + if (socket_ != INVALID_SOCKET) { + if (shutdown(/*s=*/socket_, /*how=*/SD_BOTH) == SOCKET_ERROR) { + LOG(WARNING) << "Shutdown failed with error:" << WSAGetLastError(); + result = false; + } + + if (closesocket(/*s=*/socket_) == SOCKET_ERROR) { + LOG(WARNING) << "Close socket failed with error:" << WSAGetLastError(); + result = false; + } + + socket_ = INVALID_SOCKET; + } + + LOG(INFO) << "Closed NearbyServerSocket."; + return result; +} + +} // namespace nearby::windows diff --git a/internal/platform/implementation/windows/nearby_server_socket.h b/internal/platform/implementation/windows/nearby_server_socket.h new file mode 100644 index 00000000..5bf40333 --- /dev/null +++ b/internal/platform/implementation/windows/nearby_server_socket.h @@ -0,0 +1,48 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NEARBY_SERVER_SOCKET_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NEARBY_SERVER_SOCKET_H_ + +#include + +#include +#include + +#include "internal/platform/implementation/windows/nearby_client_socket.h" + +namespace nearby::windows { + +class NearbyServerSocket { + public: + NearbyServerSocket(); + ~NearbyServerSocket(); + + bool Listen(const std::string& ip_address, int port); + std::unique_ptr Accept(); + bool Close(); + + std::string GetIPAddress() const { return ip_address_; } + int GetPort() const { return port_; } + + private: + bool is_socket_initiated_ = false; + SOCKET socket_ = INVALID_SOCKET; + std::string ip_address_; + int port_ = 0; +}; + +} // namespace nearby::windows + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NEARBY_SERVER_SOCKET_H_