#include #include #include #ifdef ESP_PLATFORM #include "lwip/sockets.h" #include "lwip/netdb.h" #include "esp_timer.h" #else #ifdef _WIN32 #include #include #else #include #include #endif #include #include #ifndef CLOCK_MONOTONIC #define CLOCK_MONOTONIC 1 #endif #endif #include "globals.h" #include "varnum.h" #include "procedures.h" #include "tools.h" #ifndef htonll static uint64_t htonll (uint64_t value) { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return ((uint64_t)htonl((uint32_t)(value >> 32))) | ((uint64_t)htonl((uint32_t)(value & 0xFFFFFFFF)) << 32); #else return value; #endif } #endif // Keep track of the total amount of bytes received with recv_all // Helps notice misread packets and clean up after errors uint64_t total_bytes_received = 0; ssize_t recv_all (int client_fd, void *buf, size_t n, uint8_t require_first) { char *p = buf; size_t total = 0; // Track time of last meaningful network update // Used to handle timeout when client is stalling int64_t last_update_time = get_program_time(); // If requested, exit early when first byte not immediately available if (require_first) { ssize_t r = recv(client_fd, p, 1, MSG_PEEK); if (r <= 0) { if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { return 0; // no first byte available yet } return -1; // error or connection closed } } // Busy-wait (with task yielding) until we get exactly n bytes while (total < n) { ssize_t r = recv(client_fd, p + total, n - total, 0); if (r < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { // handle network timeout if (get_program_time() - last_update_time > NETWORK_TIMEOUT_TIME) { disconnectClient(&client_fd, -1); return -1; } task_yield(); continue; } else { total_bytes_received += total; return -1; // real error } } else if (r == 0) { // connection closed before full read total_bytes_received += total; return total; } total += r; last_update_time = get_program_time(); } total_bytes_received += total; return total; // got exactly n bytes } ssize_t send_all (int client_fd, const void *buf, ssize_t len) { // Treat any input buffer as *uint8_t for simplicity const uint8_t *p = (const uint8_t *)buf; ssize_t sent = 0; // Track time of last meaningful network update // Used to handle timeout when client is stalling int64_t last_update_time = get_program_time(); // Busy-wait (with task yielding) until all data has been sent while (sent < len) { #ifdef _WIN32 ssize_t n = send(client_fd, p + sent, len - sent, 0); #else ssize_t n = send(client_fd, p + sent, len - sent, MSG_NOSIGNAL); #endif if (n > 0) { // some data was sent, log it sent += n; last_update_time = get_program_time(); continue; } if (n == 0) { // connection was closed, treat this as an error errno = ECONNRESET; return -1; } // not yet ready to transmit, try again #ifdef _WIN32 //handles windows socket timeout int err = WSAGetLastError(); if (err == WSAEWOULDBLOCK || err == WSAEINTR) { #else if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { #endif // handle network timeout if (get_program_time() - last_update_time > NETWORK_TIMEOUT_TIME) { disconnectClient(&client_fd, -2); return -1; } task_yield(); continue; } return -1; // real error } return sent; } ssize_t writeByte (int client_fd, uint8_t byte) { return send_all(client_fd, &byte, 1); } ssize_t writeUint16 (int client_fd, uint16_t num) { uint16_t be = htons(num); return send_all(client_fd, &be, sizeof(be)); } ssize_t writeUint32 (int client_fd, uint32_t num) { uint32_t be = htonl(num); return send_all(client_fd, &be, sizeof(be)); } ssize_t writeUint64 (int client_fd, uint64_t num) { uint64_t be = htonll(num); return send_all(client_fd, &be, sizeof(be)); } ssize_t writeFloat (int client_fd, float num) { uint32_t bits; memcpy(&bits, &num, sizeof(bits)); bits = htonl(bits); return send_all(client_fd, &bits, sizeof(bits)); } ssize_t writeDouble (int client_fd, double num) { uint64_t bits; memcpy(&bits, &num, sizeof(bits)); bits = htonll(bits); return send_all(client_fd, &bits, sizeof(bits)); } uint8_t readByte (int client_fd) { recv_count = recv_all(client_fd, recv_buffer, 1, false); return recv_buffer[0]; } uint16_t readUint16 (int client_fd) { recv_count = recv_all(client_fd, recv_buffer, 2, false); return ((uint16_t)recv_buffer[0] << 8) | recv_buffer[1]; } int16_t readInt16 (int client_fd) { recv_count = recv_all(client_fd, recv_buffer, 2, false); return ((int16_t)recv_buffer[0] << 8) | (int16_t)recv_buffer[1]; } uint32_t readUint32 (int client_fd) { recv_count = recv_all(client_fd, recv_buffer, 4, false); return ((uint32_t)recv_buffer[0] << 24) | ((uint32_t)recv_buffer[1] << 16) | ((uint32_t)recv_buffer[2] << 8) | ((uint32_t)recv_buffer[3]); } uint64_t readUint64 (int client_fd) { recv_count = recv_all(client_fd, recv_buffer, 8, false); return ((uint64_t)recv_buffer[0] << 56) | ((uint64_t)recv_buffer[1] << 48) | ((uint64_t)recv_buffer[2] << 40) | ((uint64_t)recv_buffer[3] << 32) | ((uint64_t)recv_buffer[4] << 24) | ((uint64_t)recv_buffer[5] << 16) | ((uint64_t)recv_buffer[6] << 8) | ((uint64_t)recv_buffer[7]); } int64_t readInt64 (int client_fd) { recv_count = recv_all(client_fd, recv_buffer, 8, false); return ((int64_t)recv_buffer[0] << 56) | ((int64_t)recv_buffer[1] << 48) | ((int64_t)recv_buffer[2] << 40) | ((int64_t)recv_buffer[3] << 32) | ((int64_t)recv_buffer[4] << 24) | ((int64_t)recv_buffer[5] << 16) | ((int64_t)recv_buffer[6] << 8) | ((int64_t)recv_buffer[7]); } float readFloat (int client_fd) { uint32_t bytes = readUint32(client_fd); float output; memcpy(&output, &bytes, sizeof(output)); return output; } double readDouble (int client_fd) { uint64_t bytes = readUint64(client_fd); double output; memcpy(&output, &bytes, sizeof(output)); return output; } // Receive length prefixed data with bounds checking ssize_t readLengthPrefixedData (int client_fd) { uint32_t length = readVarInt(client_fd); if (length >= MAX_RECV_BUF_LEN) { printf("ERROR: Received length (%u) exceeds maximum (%u)\n", length, MAX_RECV_BUF_LEN); disconnectClient(&client_fd, -1); recv_count = 0; return 0; } return recv_all(client_fd, recv_buffer, length, false); } // Reads a networked string into recv_buffer void readString (int client_fd) { recv_count = readLengthPrefixedData(client_fd); recv_buffer[recv_count] = '\0'; } // Reads a networked string of up to N bytes into recv_buffer void readStringN (int client_fd, uint32_t max_length) { // Forward to readString if max length is invalid if (max_length >= MAX_RECV_BUF_LEN) { readString(client_fd); return; } // Attempt to read full string within maximum uint32_t length = readVarInt(client_fd); if (max_length > length) { recv_count = recv_all(client_fd, recv_buffer, length, false); recv_buffer[recv_count] = '\0'; return; } // Read string up to maximum, dump the rest recv_count = recv_all(client_fd, recv_buffer, max_length, false); recv_buffer[recv_count] = '\0'; uint8_t dummy; for (uint32_t i = max_length; i < length; i ++) { recv_all(client_fd, &dummy, 1, false); } } uint32_t fast_rand () { rng_seed ^= rng_seed << 13; rng_seed ^= rng_seed >> 17; rng_seed ^= rng_seed << 5; return rng_seed; } uint64_t splitmix64 (uint64_t state) { uint64_t z = state + 0x9e3779b97f4a7c15; z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; z = (z ^ (z >> 27)) * 0x94d049bb133111eb; return z ^ (z >> 31); } #ifndef ESP_PLATFORM // Returns system time in microseconds. // On ESP-IDF, this is available in "esp_timer.h", and returns time *since // the start of the program*, and NOT wall clock time. To ensure // compatibility, this should only be used to measure time intervals. int64_t get_program_time () { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (int64_t)ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL; } #endif