#include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __SWITCH__ #include #include #include #endif #include #include #include static TransferState g_server_state; static std::atomic g_server_client_sock{-1}; static std::atomic g_broadcast_sock{-1}; bool isServerTransferDone() { return g_server_state.done.load(); } bool isServerTransferCancelled() { return g_server_state.cancelled.load(); } double getServerProgress() { return g_server_state.progress(); } std::string getServerStatusText() { return g_server_state.getStatus(); } void cancelServerTransfer() { g_server_state.cancelled.store(true); int sock = g_server_client_sock.load(); if (sock >= 0) shutdown(sock, SHUT_RDWR); int bsock = g_broadcast_sock.load(); if (bsock >= 0) shutdown(bsock, SHUT_RDWR); } #ifdef __SWITCH__ static std::string replaceUsername(const std::string& path) { std::string username = StringUtils::removeNotAscii( StringUtils::removeAccents(Account::username(g_currentUId))); size_t lastSlash = path.rfind('/'); if (lastSlash == std::string::npos) return path; size_t prevSlash = path.rfind('/', lastSlash - 1); if (prevSlash == std::string::npos) return username + path.substr(lastSlash); return path.substr(0, prevSlash + 1) + username + path.substr(lastSlash); } #endif static bool recv_all(int sock, void* buf, size_t len) { size_t received = 0; while (received < len) { ssize_t n = read(sock, static_cast(buf) + received, len - received); if (n <= 0) return false; received += n; } return true; } static void mkdirs(const std::string& path) { for (size_t i = 1; i < path.size(); i++) { if (path[i] == '/') { std::string component = path.substr(0, i); mkdir(component.c_str(), 0777); } } mkdir(path.c_str(), 0777); } static void receive_file(int sock, const std::string& relative_path, uint64_t file_size) { std::cout << "Receiving: " << relative_path << " (" << file_size << " bytes)" << std::endl; size_t last_slash = relative_path.rfind('/'); if (last_slash != std::string::npos) { std::string dir = relative_path.substr(0, last_slash); if (!dir.empty()) mkdirs(dir); } FILE* outfile = fopen(relative_path.c_str(), "wb"); if (!outfile) { std::cerr << "Failed to open for writing: " << relative_path << " errno=" << errno << std::endl; // Drain so sender doesn't hang std::vector drain(proto::BUF_SIZE); uint64_t remaining = file_size; while (remaining > 0) { size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE); ssize_t n = read(sock, drain.data(), to_read); if (n <= 0) break; remaining -= (uint64_t)n; } return; } g_server_state.bytes_total.store(file_size); g_server_state.bytes_done.store(0); std::vector buffer(proto::BUF_SIZE); uint64_t total_received = 0; while (total_received < file_size) { size_t to_read = (size_t)std::min(file_size - total_received, (uint64_t)proto::BUF_SIZE); ssize_t n = read(sock, buffer.data(), to_read); if (n <= 0) { std::cerr << "Read error receiving: " << relative_path << std::endl; break; } fwrite(buffer.data(), 1, (size_t)n, outfile); total_received += (uint64_t)n; g_server_state.bytes_done.store(total_received); } fclose(outfile); std::cout << "Received: " << relative_path << std::endl; } static void* handle_client(void* socket_desc) { int client_socket = *(int*)socket_desc; free(socket_desc); while (true) { uint32_t filename_len = 0; if (!recv_all(client_socket, &filename_len, sizeof(filename_len))) break; if (filename_len == proto::EOF_SENTINEL) { std::cout << "End of transfer." << std::endl; break; } if (filename_len > proto::MAX_FILENAME) { std::cerr << "filename_len=" << filename_len << " exceeds MAX_FILENAME, aborting." << std::endl; break; } std::vector filename(filename_len + 1, '\0'); if (!recv_all(client_socket, filename.data(), filename_len)) { std::cerr << "Short read on filename, aborting." << std::endl; break; } std::string filename_str(filename.data(), filename_len); #ifdef __SWITCH__ filename_str = replaceUsername(filename_str); #endif { size_t sl = filename_str.rfind('/'); g_server_state.setStatus( sl != std::string::npos ? filename_str.substr(sl + 1) : filename_str); } uint64_t file_size = 0; if (!recv_all(client_socket, &file_size, sizeof(file_size))) { std::cerr << "Short read on file_size, aborting." << std::endl; break; } receive_file(client_socket, filename_str, file_size); } close(client_socket); return nullptr; } struct AcceptArgs { int server_fd; }; static void* accept_and_handle(void* arg) { int server_fd = static_cast(arg)->server_fd; delete static_cast(arg); sockaddr_in client_addr{}; socklen_t client_len = sizeof(client_addr); int client_socket = accept(server_fd, (sockaddr*)&client_addr, &client_len); close(server_fd); if (client_socket >= 0) { g_server_client_sock.store(client_socket); int* pclient = new (std::nothrow) int(client_socket); if (pclient) handle_client(pclient); g_server_client_sock.store(-1); } g_server_state.done.store(true); return nullptr; } static void* broadcast_listener(void* arg) { Socket udp(socket(AF_INET, SOCK_DGRAM, 0)); if (!udp.valid()) { perror("broadcast_listener: socket"); return nullptr; } g_broadcast_sock.store(udp.fd); sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(proto::MULTICAST_PORT); if (bind(udp, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("broadcast_listener: bind"); g_broadcast_sock.store(-1); return nullptr; } ip_mreq group{}; group.imr_multiaddr.s_addr = inet_addr(proto::MULTICAST_GROUP); group.imr_interface.s_addr = htonl(INADDR_ANY); if (setsockopt(udp, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) { perror("broadcast_listener: setsockopt"); g_broadcast_sock.store(-1); return nullptr; } std::cout << "Broadcast listener started" << std::endl; char buf[256]; sockaddr_in from{}; socklen_t fromlen = sizeof(from); while (true) { ssize_t n = recvfrom(udp, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen); if (n < 0) { if (g_server_state.cancelled.load()) break; continue; } buf[n] = '\0'; if (strcmp(buf, "DISCOVER_SERVER") == 0) { const char* reply = "SERVER_HERE"; sendto(udp, reply, strlen(reply), 0, (sockaddr*)&from, fromlen); std::cout << "Discovery replied." << std::endl; break; } } g_broadcast_sock.store(-1); return nullptr; } int startSendingThread() { pthread_t broadcast_thread; if (pthread_create(&broadcast_thread, nullptr, broadcast_listener, nullptr) != 0) { perror("startSendingThread: broadcast thread"); return 1; } pthread_detach(broadcast_thread); Socket server(socket(AF_INET, SOCK_STREAM, 0)); if (!server.valid()) { perror("startSendingThread: socket"); return 1; } sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(proto::TCP_PORT); if (bind(server, (sockaddr*)&addr, sizeof(addr)) < 0) { perror("startSendingThread: bind"); return 1; } if (listen(server, 3) < 0) { perror("startSendingThread: listen"); return 1; } g_server_state.reset(); g_server_state.setStatus("Waiting for connection..."); AcceptArgs* acc_args = new AcceptArgs{server.fd}; pthread_t accept_thread; if (pthread_create(&accept_thread, nullptr, accept_and_handle, acc_args) != 0) { delete acc_args; return 1; } pthread_detach(accept_thread); server.release(); // accepted by accept_and_handle return 0; } #ifndef __SWITCH__ int main() { if (startSendingThread() != 0) return 1; while (!isServerTransferDone()) usleep(16000); return 0; } #endif