Files
barecopper/src/tools.c

224 lines
6.5 KiB
C

#include <stdio.h>
#include <string.h>
#include <errno.h>
#ifdef ESP_PLATFORM
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include "esp_timer.h"
#else
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC 1
#endif
#endif
#include "globals.h"
#include "varnum.h"
#include "tools.h"
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
}
// 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) 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) {
ssize_t n = send(client_fd, p + sent, len - sent, MSG_NOSIGNAL);
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
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
// handle network timeout
if (get_program_time() - last_update_time > NETWORK_TIMEOUT_TIME) return -1;
task_yield();
continue;
}
return -1; // real error
}
return 0;
}
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;
}
// Reads a networked string into recv_buffer
void readString (int client_fd) {
uint32_t length = readVarInt(client_fd);
recv_count = recv_all(client_fd, recv_buffer, length, false);
recv_buffer[recv_count] = '\0';
}
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
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