diff options
| author | 2025-09-13 23:42:29 +0330 | |
|---|---|---|
| committer | 2025-09-13 23:42:29 +0330 | |
| commit | 8f73120e81f8ee903a4c50566501ea36efe55edc (patch) | |
| tree | bcd2d0b806b5225209b408c53a181369abbda4b3 | |
| parent | plugin file support (diff) | |
client library
| -rw-r--r-- | Makefile | 6 | ||||
| -rw-r--r-- | client/client.c | 528 | ||||
| -rw-r--r-- | client/client.h | 200 | ||||
| -rw-r--r-- | client/example.c | 102 | ||||
| -rw-r--r-- | src/client.c | 440 | ||||
| -rw-r--r-- | src/server.c | 14 |
6 files changed, 845 insertions, 445 deletions
@@ -5,10 +5,10 @@ clean: sample_plugin: clang plugin_example/plugin_example.c -shared -fPIC -o bin/plugin_example.so -client: - clang src/client.c -o bin/client -lsodium -lz -O3 -g +build_client: + clang client/example.c client/client.c -o bin/client -lsodium -lz -O3 -g -server: +build_server: clang src/server.c -o bin/server -lsodium -lz -O3 -g release: clean diff --git a/client/client.c b/client/client.c new file mode 100644 index 0000000..bac1b6e --- /dev/null +++ b/client/client.c @@ -0,0 +1,528 @@ +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +// +#include "../src/common.c" +#include "../src/security.c" +#include "../src/sha256.c" + +#define MAX_BUFFER 1 << 18 +#define MAX_BODY 1 << 22 + +typedef enum { + MP_CLIENT_SUCCESS = 0, + MP_CLIENT_ERROR_NETWORK_FAIL, + MP_CLIENT_ERROR_INVALID_ADDRESS, + MP_CLIENT_ERROR_CONNECTION_FAILED, + MP_CLIENT_ERROR_SEND_FAILED, + MP_CLIENT_ERROR_RECV_FAILED, + MP_CLIENT_ERROR_SERVER_BUSY, // 418 teapot + MP_CLIENT_ERROR_INVALID_RESPONSE, + MP_CLIENT_ERROR_DESERIALIZE_FAILED, + MP_CLIENT_ERROR_ALLOC_FAILED, + MP_CLIENT_ERROR_FILE_NOT_FOUND, + MP_CLIENT_ERROR_FILE_READ_FAILED, + MP_CLIENT_ERROR_BAD_ARGUMENT, +} mp_client_error_t; + +struct data_packet_list { + struct data_packet *packets; // caller must free this + uint16_t count; + uint64_t new_offset; +}; + +static ssize_t __write_all(int fd, const void *buf, size_t len) { + size_t off = 0; + const char *p = buf; + while (off < len) { + ssize_t r = send(fd, p + off, len - off, MSG_NOSIGNAL); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; + return -1; + } + off += (size_t) r; + } + return (ssize_t) off; +} + +static int __read_exact(int fd, void *buf, size_t count) { + size_t off = 0; + char *p = buf; + while (off < count) { + ssize_t r = recv(fd, p + off, count - off, 0); + if (r < 0) { + if (errno == EINTR) continue; + return -1; + } else if (r == 0) { + return -1; + } + off += (size_t) r; + } + return 0; +} + +static mp_client_error_t __read_until_headers(int fd, char **out_buf, size_t *out_len) { + size_t cap = 4096; + size_t len = 0; + char *buf = malloc(cap); + if (!buf) return MP_CLIENT_ERROR_ALLOC_FAILED; + + while (1) { + if (cap - len < 1024) { + size_t ncap = cap * 2; + char *n = realloc(buf, ncap); + if (!n) { + free(buf); + return MP_CLIENT_ERROR_ALLOC_FAILED; + } + buf = n; + cap = ncap; + } + ssize_t r = recv(fd, buf + len, cap - len, 0); + if (r < 0) { + if (errno == EINTR) continue; + free(buf); + return MP_CLIENT_ERROR_RECV_FAILED; + } else if (r == 0) { + break; + } + len += (size_t) r; + if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4) != NULL) { break; } + if (len > MAX_BUFFER) { + free(buf); + return MP_CLIENT_ERROR_INVALID_RESPONSE; // Header too large + } + } + *out_buf = buf; + *out_len = len; + return MP_CLIENT_SUCCESS; +} + +static ssize_t __parse_content_length(const char *hdr, size_t hdr_len) { + const char *p = hdr; + const char *end = hdr + hdr_len; + while (p < end) { + const char *line_end = memchr(p, '\n', (size_t) (end - p)); + if (!line_end) line_end = end; + if ((size_t) (line_end - p) >= 15 && strncasecmp(p, "Content-Length:", 15) == 0) { + const char *num = p + 15; + while (num < line_end && (*num == ' ' || *num == '\t')) num++; + return strtol(num, NULL, 10); + } + p = line_end + 1; + } + return -1; +} + +static mp_client_error_t mp_client_request( + const char *ip, + int port, + const byte *data, + size_t data_len, + byte **response, + size_t *resp_len) { + if (data_len < 8) data_len = 8; + + *response = NULL; + *resp_len = 0; + mp_client_error_t err = MP_CLIENT_SUCCESS; + + bool is_ipv6 = strchr(ip, ':') != NULL; + int domain = is_ipv6 ? AF_INET6 : AF_INET; + + int sock = socket(domain, SOCK_STREAM, 0); + if (sock < 0) return MP_CLIENT_ERROR_NETWORK_FAIL; + + struct sockaddr_storage addr = { 0 }; + addr.ss_family = domain; + if (domain == AF_INET) { + struct sockaddr_in *addr4 = (struct sockaddr_in *) &addr; + addr4->sin_port = htons(port); + if (inet_pton(domain, ip, &addr4->sin_addr) != 1) { + err = MP_CLIENT_ERROR_INVALID_ADDRESS; + goto cleanup_sock; + } + } else { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &addr; + addr6->sin6_port = htons(port); + if (inet_pton(domain, ip, &addr6->sin6_addr) != 1) { + err = MP_CLIENT_ERROR_INVALID_ADDRESS; + goto cleanup_sock; + } + } + + struct timeval timeout = { 5, 0 }; + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + + if (connect( + sock, + (struct sockaddr *) &addr, + (domain == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) + < 0) { + err = MP_CLIENT_ERROR_CONNECTION_FAILED; + goto cleanup_sock; + } + + char req_hdr[ 256 ]; + int hdr_n = snprintf( + req_hdr, + sizeof(req_hdr), + "POST / HTTP/1.1\r\nHost: %s%s%s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n", + is_ipv6 ? "[" : "", + ip, + is_ipv6 ? "]" : "", + data_len); + + if (__write_all(sock, req_hdr, (size_t) hdr_n) < 0 + || (data_len > 0 && __write_all(sock, data, data_len) < 0)) { + err = MP_CLIENT_ERROR_SEND_FAILED; + goto cleanup_sock; + } + + char *hdr_buf = NULL; + size_t hdr_len = 0; + err = __read_until_headers(sock, &hdr_buf, &hdr_len); + if (err != MP_CLIENT_SUCCESS) { goto cleanup_sock; } + + if (memmem(hdr_buf, hdr_len, "HTTP/1.1 418", 12) != NULL) { + err = MP_CLIENT_ERROR_SERVER_BUSY; + free(hdr_buf); + goto cleanup_sock; + } + + char *sep = memmem(hdr_buf, hdr_len, "\r\n\r\n", 4); + byte *body_ptr = NULL; + size_t body_len = 0; + + if (sep) { + size_t headers_bytes = (size_t) (sep - hdr_buf) + 4; + ssize_t cl = __parse_content_length(hdr_buf, headers_bytes); + size_t already = hdr_len - headers_bytes; + if (cl > 0) { + if ((size_t) cl > MAX_BODY) cl = MAX_BODY; + body_ptr = malloc((size_t) cl); + if (!body_ptr) { + err = MP_CLIENT_ERROR_ALLOC_FAILED; + free(hdr_buf); + goto cleanup_sock; + } + size_t to_copy = already > (size_t) cl ? (size_t) cl : already; + memcpy(body_ptr, sep + 4, to_copy); + body_len = to_copy; + + if ((size_t) body_len < (size_t) cl) { + if (__read_exact(sock, body_ptr + body_len, (size_t) cl - body_len) < 0) { + free(body_ptr); + err = MP_CLIENT_ERROR_RECV_FAILED; + free(hdr_buf); + goto cleanup_sock; + } + body_len = (size_t) cl; + } + } + } + + free(hdr_buf); + *response = body_ptr; + *resp_len = body_len; + +cleanup_sock: + close(sock); + return err; +} + +mp_client_error_t mp_get_server_info(const char *ip, int port, struct relay_info *out_info) { + byte data[] = { audit_server }; + byte *response = NULL; + size_t resp_len = 0; + + mp_client_error_t ret = mp_client_request(ip, port, data, sizeof(data), &response, &resp_len); + if (ret != MP_CLIENT_SUCCESS) { + free(response); + return ret; + } + + if (resp_len < 1 || response[ 0 ] != audit_response) { + free(response); + return MP_CLIENT_ERROR_INVALID_RESPONSE; + } + + bool success; + *out_info = s2relay_info(resp_len - 1, response + 1, &success); + free(response); + + return success ? MP_CLIENT_SUCCESS : MP_CLIENT_ERROR_DESERIALIZE_FAILED; +} + +mp_client_error_t mp_get_server_neighbors( + const char *ip, int port, byte count, struct relay_neighbor **out_neighbors, byte *out_count) { + byte data[] = { get_neighbors, count }; + byte *response = NULL; + size_t resp_len = 0; + *out_neighbors = NULL; + *out_count = 0; + + mp_client_error_t ret = mp_client_request(ip, port, data, sizeof(data), &response, &resp_len); + if (ret != MP_CLIENT_SUCCESS) { + free(response); + return ret; + } + + if (resp_len < 1 || response[ 0 ] != neighbors_info) { + free(response); + return MP_CLIENT_ERROR_INVALID_RESPONSE; + } + + size_t num_neighbors = (resp_len - 1) / RELAY_NEIGHBOR_WIRE_LEN; + if (num_neighbors == 0) { + free(response); + return MP_CLIENT_SUCCESS; + } + + *out_neighbors = malloc(num_neighbors * sizeof(struct relay_neighbor)); + if (!*out_neighbors) { + free(response); + return MP_CLIENT_ERROR_ALLOC_FAILED; + } + + byte actual_count = 0; + for (size_t i = 0; i < num_neighbors; ++i) { + bool success; + (*out_neighbors)[ i ] = s2relay_neighbor( + RELAY_NEIGHBOR_WIRE_LEN, response + 1 + (i * RELAY_NEIGHBOR_WIRE_LEN), &success); + if (success) { actual_count++; } + } + *out_count = actual_count; + + free(response); + return MP_CLIENT_SUCCESS; +} + +mp_client_error_t +mp_push_packet(const char *ip, int port, struct data_packet packet, bool *out_success) { + const size_t send_len = 1 + PACKET_SIZE; + byte *send_data = malloc(send_len); + if (!send_data) return MP_CLIENT_ERROR_ALLOC_FAILED; + + send_data[ 0 ] = push_packet; + data_packet2s(packet, send_data + 1); + + byte *response = NULL; + size_t resp_len = 0; + *out_success = false; + + mp_client_error_t ret = mp_client_request(ip, port, send_data, send_len, &response, &resp_len); + free(send_data); + + if (ret != MP_CLIENT_SUCCESS) { + free(response); + return ret; + } + + if (resp_len == 2 && response[ 0 ] == push_packet_result) { + *out_success = response[ 1 ]; + } else { + ret = MP_CLIENT_ERROR_INVALID_RESPONSE; + } + + free(response); + return ret; +} + +mp_client_error_t +mp_get_packet(const char *ip, int port, const hash packet_hash, struct data_packet *out_packet) { + const size_t send_len = 1 + 32; + byte send_data[ send_len ]; + send_data[ 0 ] = get_packet; + memcpy(send_data + 1, packet_hash, 32); + + byte *response = NULL; + size_t resp_len = 0; + + mp_client_error_t ret = mp_client_request(ip, port, send_data, send_len, &response, &resp_len); + if (ret != MP_CLIENT_SUCCESS) { + free(response); + return ret; + } + + if (resp_len < 1 || response[ 0 ] != get_packet_result) { + free(response); + return MP_CLIENT_ERROR_INVALID_RESPONSE; + } + + bool success; + *out_packet = s2data_packet(resp_len - 1, response + 1, &success); + free(response); + + return success ? MP_CLIENT_SUCCESS : MP_CLIENT_ERROR_DESERIALIZE_FAILED; +} + +mp_client_error_t mp_get_firehose_packets( + const char *ip, int port, uint64_t offset, uint16_t count, struct data_packet_list *out_list) { + const size_t send_len = 1 + 1 + 8; + byte send_data[ send_len ]; + send_data[ 0 ] = firehose; + firehose_request2s( + (struct firehose_request) { .count = (uint8_t) count, .offset = offset }, send_data + 1); + + byte *response = NULL; + size_t resp_len = 0; + out_list->packets = NULL; + out_list->count = 0; + out_list->new_offset = offset; + + mp_client_error_t ret = mp_client_request(ip, port, send_data, send_len, &response, &resp_len); + if (ret != MP_CLIENT_SUCCESS) { + free(response); + return ret; + } + + if (resp_len < 9 || response[ 0 ] != firehose_reply) { + free(response); + return MP_CLIENT_ERROR_INVALID_RESPONSE; + } + + out_list->new_offset = read_u64_le(response + 1); + + size_t available_bytes = resp_len - 9; + size_t available_packets = available_bytes / PACKET_SIZE; + if (available_packets == 0) { + free(response); + return MP_CLIENT_SUCCESS; + } + + out_list->packets = malloc(available_packets * sizeof(struct data_packet)); + if (!out_list->packets) { + free(response); + return MP_CLIENT_ERROR_ALLOC_FAILED; + } + + out_list->count = 0; + for (size_t i = 0; i < available_packets; i++) { + bool success; + struct data_packet packet + = s2data_packet(PACKET_SIZE, response + 9 + (i * PACKET_SIZE), &success); + if (success) { out_list->packets[ out_list->count++ ] = packet; } + } + + free(response); + return MP_CLIENT_SUCCESS; +} + +struct data_packet mp_create_packet_from_bytes( + const byte *input, + size_t input_len, + byte recipient_count, + hash *recipients, + keypair author_keys) { + struct data_packet packet; + memset(&packet, 0, sizeof(packet)); + + size_t copy_len = input_len > 65536 ? 65536 : input_len; + if (input && copy_len > 0) memcpy(packet.contents.data, input, copy_len); + + packet.contents.recipient_count + = recipient_count > MAX_RECIPIENTS ? MAX_RECIPIENTS : recipient_count; + if (recipients) + memcpy(packet.contents.recipients, recipients, packet.contents.recipient_count * 32); + + packet.contents.created_time_ms = get_current_time_ms(); + + memcpy(packet.contents.author, author_keys.encryption_pub, sizeof(public_key)); + memcpy(packet.contents.author_signing_pub, author_keys.signing_pub, sizeof(public_key)); + packet.contents.encrypted = false; + + // Create signature + size_t msg_len = CONTENTS_SIZE - sizeof(signature); + byte *msg_buf = malloc(msg_len); + if (msg_buf) { + msg_buf[ 0 ] = packet.contents.encrypted ? 1 : 0; + write_u64_le(msg_buf + 1, packet.contents.created_time_ms); + memcpy(msg_buf + 9, packet.contents.author, 32); + memcpy(msg_buf + 41, packet.contents.author_signing_pub, 32); + msg_buf[ 73 ] = packet.contents.recipient_count; + memcpy(msg_buf + 74, packet.contents.recipients, 32 * MAX_RECIPIENTS); + memcpy(msg_buf + 74 + 32 * MAX_RECIPIENTS, packet.contents.data, 65536); + crypto_sign_detached( + packet.contents.signature, NULL, msg_buf, msg_len, author_keys.signing_priv); + free(msg_buf); + } + + return packet; +} + +mp_client_error_t mp_create_packet_from_file( + const char *filename, + byte recipient_count, + hash *recipients, + keypair author_keys, + struct data_packet *out_packet) { + FILE *file = fopen(filename, "rb"); + if (!file) return MP_CLIENT_ERROR_FILE_NOT_FOUND; + + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (file_size < 0) { + fclose(file); + return MP_CLIENT_ERROR_FILE_READ_FAILED; + } + + byte *file_buf = calloc(1, 65536); + if (!file_buf) { + fclose(file); + return MP_CLIENT_ERROR_ALLOC_FAILED; + } + + size_t to_read = file_size > 65536 ? 65536 : file_size; + if (fread(file_buf, 1, to_read, file) != to_read) { + free(file_buf); + fclose(file); + return MP_CLIENT_ERROR_FILE_READ_FAILED; + } + fclose(file); + + *out_packet + = mp_create_packet_from_bytes(file_buf, to_read, recipient_count, recipients, author_keys); + free(file_buf); + return MP_CLIENT_SUCCESS; +} + +struct data_packet mp_run_pow(struct data_packet packet, byte leading_zeros) { + const size_t hash_input_len = 4 + CONTENTS_SIZE; + byte hash_input_buffer[ hash_input_len ]; + + byte *contents_buffer = hash_input_buffer + 4; + contents_buffer[ 0 ] = packet.contents.encrypted ? 1 : 0; + write_u64_le(contents_buffer + 1, packet.contents.created_time_ms); + memcpy(contents_buffer + 9, packet.contents.author, 32); + memcpy(contents_buffer + 41, packet.contents.author_signing_pub, 32); + contents_buffer[ 73 ] = packet.contents.recipient_count; + memcpy(contents_buffer + 74, packet.contents.recipients, 32 * MAX_RECIPIENTS); + memcpy(contents_buffer + 74 + 32 * MAX_RECIPIENTS, packet.contents.data, 65536); + memcpy(contents_buffer + 74 + 32 * MAX_RECIPIENTS + 65536, packet.contents.signature, 64); + + packet.nonce = 0; + while (true) { + write_u32_le(hash_input_buffer, packet.nonce); + struct sha256 calculated_hash_struct = sha256_hash(hash_input_buffer, hash_input_len); + memcpy(packet.hash, calculated_hash_struct.hash, 32); + if (check_leading_zeros(packet.hash, leading_zeros)) { break; } + packet.nonce++; + } + + return packet; +} + +keypair mp_generate_keypair(void) { return generate_keypair(); }
\ No newline at end of file diff --git a/client/client.h b/client/client.h new file mode 100644 index 0000000..ee94d21 --- /dev/null +++ b/client/client.h @@ -0,0 +1,200 @@ +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +typedef uint8_t byte; +typedef byte hash[ 32 ]; +typedef byte public_key[ 32 ]; +typedef byte private_key[ 32 ]; +typedef byte signature[ 64 ]; +typedef byte ipv4[ 4 ]; +typedef byte ipv6[ 16 ]; + +#define MAX_RECIPIENTS 32 + +struct relay_neighbor { + bool active; + bool has_ipv4; + bool has_ipv6; + ipv4 v4_addr; + ipv6 v6_addr; +}; + +struct relay_info { + byte major_version; + byte minor_version; + byte motd[ 128 ]; + struct { + uint16_t retention_hours; + bool can_store; + byte leading_zeros; + bool accepts_plain; + } capabilities; + struct { + uint16_t neighbors; + uint64_t held_packets; + struct relay_neighbor quick_connect[ 4 ]; + } statistics; +}; + +struct data_packet { + uint32_t nonce; + hash hash; + struct { + bool encrypted; + uint64_t created_time_ms; + public_key author; + public_key author_signing_pub; + uint8_t recipient_count; + public_key recipients[ 32 ]; + byte data[ 65536 ]; + signature signature; + } contents; +}; + +// from libsodium +#ifndef SIGN_PUBLICKEYBYTES + #define SIGN_PUBLICKEYBYTES 32 +#endif +#ifndef SIGN_SECRETKEYBYTES + #define SIGN_SECRETKEYBYTES 64 +#endif + +typedef unsigned char sign_public_key[ SIGN_PUBLICKEYBYTES ]; +typedef unsigned char sign_secret_key[ SIGN_SECRETKEYBYTES ]; + +typedef struct { + public_key encryption_pub; + private_key encryption_priv; + sign_public_key signing_pub; + sign_secret_key signing_priv; +} keypair; + +struct data_packet_list { + struct data_packet* packets; // caller must free this + uint16_t count; + uint64_t new_offset; +}; + +typedef enum { + MP_CLIENT_SUCCESS = 0, + MP_CLIENT_ERROR_NETWORK_FAIL, + MP_CLIENT_ERROR_INVALID_ADDRESS, + MP_CLIENT_ERROR_CONNECTION_FAILED, + MP_CLIENT_ERROR_SEND_FAILED, + MP_CLIENT_ERROR_RECV_FAILED, + MP_CLIENT_ERROR_SERVER_BUSY, // 418 teapot + MP_CLIENT_ERROR_INVALID_RESPONSE, + MP_CLIENT_ERROR_DESERIALIZE_FAILED, + MP_CLIENT_ERROR_ALLOC_FAILED, + MP_CLIENT_ERROR_FILE_NOT_FOUND, + MP_CLIENT_ERROR_FILE_READ_FAILED, + MP_CLIENT_ERROR_BAD_ARGUMENT, +} mp_client_error_t; + +/** + * @brief Get server information and capabilities. + * @param ip Server IP address (IPv4 or IPv6). + * @param port Server port. + * @param out_info Pointer to a struct where the server info will be stored. + * @return MP_CLIENT_SUCCESS on success, or an error code on failure. + */ +mp_client_error_t mp_get_server_info(const char* ip, int port, struct relay_info* out_info); + +/** + * @brief Get a list of neighbors from the server. + * @param ip Server IP address. + * @param port Server port. + * @param count Number of neighbors to request. + * @param out_neighbors Pointer to a pointer that will be updated to point to a newly allocated + * array of neighbors. The caller is responsible for freeing this memory. + * @param out_count Pointer to a byte where the actual number of received neighbors will be stored. + * @return MP_CLIENT_SUCCESS on success, or an error code on failure. + */ +mp_client_error_t mp_get_server_neighbors( + const char* ip, int port, byte count, struct relay_neighbor** out_neighbors, byte* out_count); + +/** + * @brief Push a packet to the server. + * @param ip Server IP address. + * @param port Server port. + * @param packet The data packet to push. + * @param out_success Pointer to a boolean that will be true if the server accepted the packet, + * false otherwise. + * @return MP_CLIENT_SUCCESS on success, or an error code on failure. + */ +mp_client_error_t +mp_push_packet(const char* ip, int port, struct data_packet packet, bool* out_success); + +/** + * @brief Get a specific packet from the server by its hash. + * @param ip Server IP address. + * @param port Server port. + * @param packet_hash The hash of the packet to retrieve. + * @param out_packet Pointer to a struct where the retrieved packet will be stored. + * @return MP_CLIENT_SUCCESS on success, or an error code on failure. + */ +mp_client_error_t +mp_get_packet(const char* ip, int port, const hash packet_hash, struct data_packet* out_packet); + +/** + * @brief Get a batch of packets from the server's firehose stream. + * @param ip Server IP address. + * @param port Server port. + * @param offset The starting offset in the firehose stream. + * @param count The maximum number of packets to retrieve. + * @param out_list Pointer to a data_packet_list struct. The 'packets' member will be allocated by + * the function and must be freed by the caller. + * @return MP_CLIENT_SUCCESS on success, or an error code on failure. + */ +mp_client_error_t mp_get_firehose_packets( + const char* ip, int port, uint64_t offset, uint16_t count, struct data_packet_list* out_list); + +/** + * @brief Create a data packet from a raw byte buffer. + * @param input The raw data buffer. + * @param input_len The length of the data buffer. + * @param recipient_count The number of recipients. + * @param recipients An array of recipient public keys. + * @param author_keys The keypair of the packet author. + * @return A new data_packet struct. + */ +struct data_packet mp_create_packet_from_bytes( + const byte* input, + size_t input_len, + byte recipient_count, + hash* recipients, + keypair author_keys); + +/** + * @brief Create a data packet by reading the contents from a file. + * @param filename The path to the file. + * @param recipient_count The number of recipients. + * @param recipients An array of recipient public keys. + * @param author_keys The keypair of the packet author. + * @param out_packet Pointer to a struct where the created packet will be stored. + * @return MP_CLIENT_SUCCESS on success, or an error code on failure. + */ +mp_client_error_t mp_create_packet_from_file( + const char* filename, + byte recipient_count, + hash* recipients, + keypair author_keys, + struct data_packet* out_packet); + +/** + * @brief Run Proof-of-Work on a packet to find a nonce that satisfies the leading zeros + * requirement. + * @param packet The input packet. The 'nonce' and 'hash' fields will be modified. + * @param leading_zeros The number of leading zero bits required for the hash. + * @return The modified packet with a valid nonce and hash. + */ +struct data_packet mp_run_pow(struct data_packet packet, byte leading_zeros); + +/** + * @brief Generates a new encryption and signing keypair. + * @return A new keypair struct. + */ +keypair mp_generate_keypair(void);
\ No newline at end of file diff --git a/client/example.c b/client/example.c new file mode 100644 index 0000000..e2384d0 --- /dev/null +++ b/client/example.c @@ -0,0 +1,102 @@ +#include "client.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// helper to print a hash in hex format +void print_hash(const hash h) { + for (int i = 0; i < 32; ++i) { printf("%02x", h[ i ]); } +} + +// helper to print an IP address +void print_ip(const struct relay_neighbor *n) { + if (n->has_ipv4) { + printf("%d.%d.%d.%d", n->v4_addr[ 0 ], n->v4_addr[ 1 ], n->v4_addr[ 2 ], n->v4_addr[ 3 ]); + } else if (n->has_ipv6) { + char ip_string[ 46 ]; + int offset = 0; + for (int j = 0; j < 8; ++j) { + uint16_t word = (n->v6_addr[ j * 2 ] << 8) | n->v6_addr[ j * 2 + 1 ]; + offset += snprintf( + ip_string + offset, sizeof(ip_string) - offset, "%x%s", word, (j < 7) ? ":" : ""); + } + printf("%s", ip_string); + } else { + printf("N/A"); + } +} + +int main() { + printf("moonpaste client library test!\n\n"); + + const char *server_ip = "127.0.0.1"; + const int server_port = 8040; + mp_client_error_t err; + + // 1. audit the server + printf("Auditing server at %s:%d...\n", server_ip, server_port); + struct relay_info info; + err = mp_get_server_info(server_ip, server_port, &info); + + if (err != MP_CLIENT_SUCCESS) { + fprintf(stderr, "Failed to audit server. Error code: %d\n", err); + return 1; + } + printf(" Server Audit Complete:\n"); + printf(" - Version: v%d.%d\n", info.major_version, info.minor_version); + printf(" - MOTD: %s\n", info.motd); + printf(" - PoW Zeros: %d\n", info.capabilities.leading_zeros); + printf(" - Held Packets: %lu\n\n", info.statistics.held_packets); + + // 2. create and push a new packet + printf("Generating keypair and creating a packet...\n"); + keypair author = mp_generate_keypair(); + const char message[] = "This is a test message from the moonpaste client library."; + struct data_packet new_packet + = mp_create_packet_from_bytes((const byte *) message, strlen(message), 0, NULL, author); + + printf("Running Proof-of-Work with %d leading zeros...\n", info.capabilities.leading_zeros); + new_packet = mp_run_pow(new_packet, info.capabilities.leading_zeros); + printf(" PoW complete. Nonce: %u, Hash: ", new_packet.nonce); + print_hash(new_packet.hash); + printf("\n"); + + printf("Pushing packet to server...\n"); + bool success; + err = mp_push_packet(server_ip, server_port, new_packet, &success); + if (err != MP_CLIENT_SUCCESS || !success) { + fprintf(stderr, "Failed to push packet. Error: %d, Success: %d\n", err, success); + } else { + printf(" Packet pushed successfully!\n\n"); + } + + // 3. retrieve the packet we just pushed + printf("Retrieving the packet by hash...\n"); + struct data_packet retrieved_packet; + err = mp_get_packet(server_ip, server_port, new_packet.hash, &retrieved_packet); + if (err != MP_CLIENT_SUCCESS) { + fprintf(stderr, "Failed to retrieve packet. Error: %d\n", err); + } else { + printf(" Packet retrieved successfully!\n"); + printf(" Contents: '%.*s'\n\n", (int) strlen(message), retrieved_packet.contents.data); + } + + // 4. get a list of neighbors + printf("Requesting 5 neighbors...\n"); + struct relay_neighbor *neighbors = NULL; + byte neighbor_count = 0; + err = mp_get_server_neighbors(server_ip, server_port, 5, &neighbors, &neighbor_count); + if (err != MP_CLIENT_SUCCESS) { + fprintf(stderr, "Failed to get neighbors. Error: %d\n", err); + } else { + printf(" Received %d neighbors:\n", neighbor_count); + for (byte i = 0; i < neighbor_count; ++i) { + printf(" - Neighbor %d: ", i + 1); + print_ip(&neighbors[ i ]); + printf("\n"); + } + free(neighbors); // free me + } + + return 0; +} diff --git a/src/client.c b/src/client.c deleted file mode 100644 index ac47307..0000000 --- a/src/client.c +++ /dev/null @@ -1,440 +0,0 @@ -#include <arpa/inet.h> -#include <fcntl.h> -#include <pthread.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <unistd.h> -// -#include "common.c" -#include "firehose.c" -#include "omen.c" -#include "packet_storage.c" -#include "security.c" -#include "sha256.c" - -struct data_packet_list { - struct data_packet* packets; - uint16_t count; - uint64_t new_offset; -}; - -struct data_packet create_packet_from_bytes( - const byte* input, - size_t input_len, - byte recipient_count, - hash* recipients, - keypair author_keys) { - struct data_packet packet; - memset(&packet, 0, sizeof(packet)); - - if (input_len > 65536) input_len = 65536; - if (input_len && input) memcpy(packet.contents.data, input, input_len); - if (input_len < 65536) memset(packet.contents.data + input_len, 0, 65536 - input_len); - - if (recipient_count > MAX_RECIPIENTS) recipient_count = MAX_RECIPIENTS; - packet.contents.recipient_count = recipient_count; - if (recipients) memcpy(packet.contents.recipients, recipients, recipient_count * 32); - - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - packet.contents.created_time_ms = (uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000; - - memcpy(packet.contents.author, author_keys.encryption_pub, sizeof(public_key)); - memcpy(packet.contents.author_signing_pub, author_keys.signing_pub, sizeof(public_key)); - packet.contents.encrypted = false; - - size_t msg_len = CONTENTS_SIZE - sizeof(signature); - byte msg_buf[ msg_len ]; - msg_buf[ 0 ] = packet.contents.encrypted ? 1 : 0; - write_u64_le(msg_buf + 1, packet.contents.created_time_ms); - memcpy(msg_buf + 9, packet.contents.author, 32); - memcpy(msg_buf + 41, packet.contents.author_signing_pub, 32); - msg_buf[ 73 ] = packet.contents.recipient_count; - memcpy(msg_buf + 74, packet.contents.recipients, 32 * MAX_RECIPIENTS); - memcpy(msg_buf + 74 + 32 * MAX_RECIPIENTS, packet.contents.data, 65536); - - crypto_sign_detached( - packet.contents.signature, NULL, msg_buf, msg_len, author_keys.signing_priv); - - return packet; -} - -struct data_packet -create_packet(const char* filename, keypair author, byte recipient_count, hash* recipients) { - byte file_buf[ 65536 ]; - memset(file_buf, 0, sizeof(file_buf)); - - long file_size = 0; - FILE* file = fopen(filename, "rb"); - if (!file) { - perror("Failed to open file"); - exit(1); - } - if (fseek(file, 0, SEEK_END) == 0) { - file_size = ftell(file); - fseek(file, 0, SEEK_SET); - } - if (file_size < 0) file_size = 0; - if (file_size > 65536) file_size = 65536; - if (file_size > 0) fread(file_buf, 1, (size_t) file_size, file); - fclose(file); - - return create_packet_from_bytes( - file_buf, (size_t) file_size, recipient_count, recipients, author); -} - -struct data_packet run_pow(struct data_packet packet, byte leading_zeros) { - printf("running Proof-of-Work with %i leading zeros...\n", leading_zeros); - uint64_t start_time = get_current_time_ms(); - - const size_t hash_input_len = 4 + CONTENTS_SIZE; - byte hash_input_buffer[ hash_input_len ]; - - byte* contents_buffer = hash_input_buffer + 4; - contents_buffer[ 0 ] = packet.contents.encrypted ? 1 : 0; - write_u64_le(contents_buffer + 1, packet.contents.created_time_ms); - memcpy(contents_buffer + 9, packet.contents.author, 32); - memcpy(contents_buffer + 41, packet.contents.author_signing_pub, 32); - contents_buffer[ 73 ] = packet.contents.recipient_count; - memcpy(contents_buffer + 74, packet.contents.recipients, 32 * MAX_RECIPIENTS); - memcpy(contents_buffer + 74 + 32 * MAX_RECIPIENTS, packet.contents.data, 65536); - memcpy(contents_buffer + 74 + 32 * MAX_RECIPIENTS + 65536, packet.contents.signature, 64); - - packet.nonce = 0; - while (true) { - memcpy(hash_input_buffer, &packet.nonce, 4); - struct sha256 calculated_hash_struct = sha256_hash(hash_input_buffer, hash_input_len); - memcpy(packet.hash, calculated_hash_struct.hash, 32); - if (check_leading_zeros(packet.hash, leading_zeros)) { break; } - packet.nonce++; - } - - uint64_t end_time = get_current_time_ms(); - printf("PoW completed in %lu ms. nonce: %u\n", end_time - start_time, packet.nonce); - return packet; -} - -struct relay_neighbor* get_server_neighbors(const char* ip, int port, byte count) { - byte data[ 8 ] = { get_neighbors, count, 0, 0, 0, 0, 0, 0 }; - byte* response = NULL; - size_t resp_len = 0; - - int ret = study(ip, port, data, sizeof(data), &response, &resp_len); - if (ret != 0) { - printf("Error: study returned %i\n", ret); - if (response) free(response); - return NULL; - } - - if (resp_len < 1) { - printf("Error: Invalid get_neighbors response\n"); - if (response) free(response); - return NULL; - } - - if (response[ 0 ] != neighbors_info) { - printf("Error: Server returned an error for get_neighbors request.\n"); - if (response) free(response); - return NULL; - } - - size_t expected_wire = 1 + (size_t) RELAY_NEIGHBOR_WIRE_LEN * count; - if (resp_len < expected_wire) { - printf( - "Error: get_neighbors response too short (expected %zu, got %zu)\n", - expected_wire, - resp_len); - if (response) free(response); - return NULL; - } - - struct relay_neighbor* neighbors = malloc(count * sizeof(struct relay_neighbor)); - if (!neighbors) { - perror("Failed to allocate memory for neighbors"); - if (response) free(response); - return NULL; - } - - for (byte i = 0; i < count; ++i) { - bool success; - neighbors[ i ] = s2relay_neighbor( - RELAY_NEIGHBOR_WIRE_LEN, response + 1 + (i * RELAY_NEIGHBOR_WIRE_LEN), &success); - if (!success) { printf("Error: Failed to deserialize neighbor %i\n", i); } - } - - free(response); - return neighbors; -} - -bool push_packet_to_server(const char* ip, int port, struct data_packet packet) { - const size_t send_len = 1 + PACKET_SIZE; - byte send_data[ send_len ]; - - send_data[ 0 ] = push_packet; - data_packet2s(packet, send_data + 1); - - byte* response = NULL; - size_t resp_len = 0; - bool success = false; - - int ret = study(ip, port, send_data, send_len, &response, &resp_len); - if (ret != 0) { - printf("error: study returned %i\n", ret); - goto cleanup; - } - - if (resp_len == 2 && response[ 0 ] == push_packet_result) { - success = response[ 1 ]; - } else { - printf("error: invalid push response\n"); - } - -cleanup: - if (response) free(response); - return success; -} - -struct data_packet get_packet_from_server(const char* ip, int port, hash packet_hash) { - const size_t send_len = 1 + 32; - byte send_data[ send_len ]; - send_data[ 0 ] = get_packet; - memcpy(send_data + 1, packet_hash, 32); - - byte* response = NULL; - size_t resp_len = 0; - bool success = false; - - int ret = study(ip, port, send_data, send_len, &response, &resp_len); - if (ret != 0) { - printf("Error: study returned %i\n", ret); - if (response) free(response); - return (struct data_packet) { 0 }; - } - - if (resp_len < 1 || response[ 0 ] != get_packet_result) { - printf("Error: Invalid get_packet response\n"); - if (response) free(response); - return (struct data_packet) { 0 }; - } - - struct data_packet packet = s2data_packet(resp_len - 1, response + 1, &success); - if (!success) { printf("Error: Failed to deserialize data packet\n"); } - - free(response); - return packet; -} - -struct data_packet_list -get_firehose_packets(const char* ip, int port, uint64_t offset, uint16_t count) { - const size_t send_len = 1 + 1 + 8; - byte send_data[ send_len ]; - send_data[ 0 ] = firehose; - firehose_request2s( - (struct firehose_request) { .count = (uint8_t) count, .offset = offset }, send_data + 1); - - byte* response = NULL; - size_t resp_len = 0; - bool success = false; - - int ret = study(ip, port, send_data, send_len, &response, &resp_len); - if (ret != 0) { - printf("Error: study returned %i\n", ret); - if (response) free(response); - return (struct data_packet_list) { 0 }; - } - - if (resp_len < 9 || response[ 0 ] != firehose_reply) { - printf("Error: Invalid firehose response\n"); - if (response) free(response); - return (struct data_packet_list) { 0 }; - } - - uint64_t new_offset = read_u64_le(response + 1); - struct data_packet_list list = { .count = 0, .new_offset = new_offset, .packets = NULL }; - - size_t available_bytes = resp_len - 9; - size_t available_packets = available_bytes / PACKET_SIZE; - if (available_packets == 0) { - free(response); - return list; - } - - if (available_packets > SIZE_MAX / sizeof(struct data_packet)) { - perror("allocation overflow"); - free(response); - return (struct data_packet_list) { 0 }; - } - - list.packets = malloc(available_packets * sizeof(struct data_packet)); - if (!list.packets) { - perror("Failed to allocate memory for packet list"); - free(response); - return (struct data_packet_list) { 0 }; - } - - size_t current_offset = 9; - while (current_offset + PACKET_SIZE <= resp_len && list.count < available_packets) { - struct data_packet packet = s2data_packet(PACKET_SIZE, response + current_offset, &success); - if (success) { - list.packets[ list.count ] = packet; - list.count++; - current_offset += PACKET_SIZE; - } else { - printf("Error: Failed to deserialize a packet from the firehose stream\n"); - break; - } - } - - free(response); - return list; -} - -void get_firehose_stream(const char* ip, int port, uint64_t offset, uint16_t count) { - size_t send_len = 1 + 1 + 8; - byte send_data[ send_len ]; - send_data[ 0 ] = (byte) firehose; - - byte* response = NULL; - size_t resp_len = 0; - bool success = false; - - uint64_t last_offset = offset; - while (true) { - firehose_request2s( - (struct firehose_request) { .count = (uint8_t) count, .offset = last_offset }, - send_data + 1); - int ret = study(ip, port, send_data, send_len, &response, &resp_len); - if (ret == -1) { - printf("study returned -1\n"); - return; - } - - if (ret != 0 || resp_len < 9 || response[ 0 ] != firehose_reply) { - free(response); - sleep(5); - continue; - } - - uint64_t server_offset = read_u64_le(response + 1); - size_t available_bytes = resp_len - 9; - size_t available_packets = available_bytes / PACKET_SIZE; - - if (available_packets == 0) { - free(response); - sleep(5); - continue; - } - - size_t current_offset = 9; - for (size_t i = 0; i < available_packets; i++) { - struct data_packet packet - = s2data_packet(PACKET_SIZE, response + current_offset, &success); - if (!success) break; - printf("streamed " hash_fmt "\n", hash_ref(packet.hash)); - current_offset += PACKET_SIZE; - } - - printf("offset: %li\n", last_offset); - last_offset += available_packets; - free(response); - // sleep(5); - } -} - -int main() { - struct relay_info info = run_server_audit("127.0.0.1", 8040); - - printf("Server Audit Complete:\n"); - printf(" Version: v%i.%i\n", info.major_version, info.minor_version); - printf(" MOTD: %s\n", info.motd); - printf(" Leading Zeros Required: %i\n", info.capabilities.leading_zeros); - printf(" Retention: %i hours\n", info.capabilities.retention_hours); - printf(" Neighbors: %u\n", info.statistics.neighbors); - printf(" Held Packets: %lu\n", info.statistics.held_packets); - - keypair author = generate_keypair(); - - const char input[] = "this is a test message from the moonpaste client."; - - struct data_packet new_packet - = create_packet_from_bytes((byte*) input, sizeof(input), 0, NULL, author); - new_packet = run_pow(new_packet, info.capabilities.leading_zeros); - printf("hash: " hash_fmt "\n", hash_ref(new_packet.hash)); - - printf("pushing new packet to server...\n"); - if (push_packet_to_server("127.0.0.1", 8040, new_packet)) { - printf("packet pushed successfully!\n"); - } else { - printf("failed to push packet.\n"); - } - - printf( - "attempting to retrieve the packet with hash " hash_fmt "...\n", hash_ref(new_packet.hash)); - struct data_packet retrieved_packet - = get_packet_from_server("127.0.0.1", 8040, new_packet.hash); - if (retrieved_packet.nonce != 0) { - printf("packet retrieved successfully! Contents:\n"); - for (int i = 0; i < 65536; ++i) { - printf("%c", retrieved_packet.contents.data[ i ]); - if (!retrieved_packet.contents.data[ i ]) break; - } - printf("\n"); - } else { - printf("failed to retrieve packet.\n"); - } - - printf("requesting neighbor list...\n"); - byte requested_neighbors = 5; - struct relay_neighbor* neighbors = get_server_neighbors("127.0.0.1", 8040, requested_neighbors); - if (neighbors) { - printf("received %i neighbors.\n", (int) requested_neighbors); - for (int i = 0; i < requested_neighbors; ++i) { - printf("Neighbor %i: ", i + 1); - if (neighbors[ i ].has_ipv4) { - printf( - "%d.%d.%d.%d\n", - neighbors[ i ].v4_addr[ 0 ], - neighbors[ i ].v4_addr[ 1 ], - neighbors[ i ].v4_addr[ 2 ], - neighbors[ i ].v4_addr[ 3 ]); - } else if (neighbors[ i ].has_ipv6) { - char ip_string[ 46 ]; - int offset = 0; - for (int j = 0; j < 8; ++j) { - uint16_t word = (neighbors[ i ].v6_addr[ j * 2 ] << 8) - | neighbors[ i ].v6_addr[ j * 2 + 1 ]; - offset += snprintf( - ip_string + offset, - sizeof(ip_string) - offset, - "%x%s", - word, - (j < 7) ? ":" : ""); - } - printf("%s\n", ip_string); - } - } - free(neighbors); - } else { - printf("Failed to get neighbors.\n"); - } - - printf("\nstarting firehose stream. Press Ctrl+C to exit.\n"); - get_firehose_stream("127.0.0.1", 8040, 0, 10); - - return 0; -} - -#define hash_fmt \ - "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \ - "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" - -#define hash_ref(_hash) \ - _hash[ 0 ], _hash[ 1 ], _hash[ 2 ], _hash[ 3 ], _hash[ 4 ], _hash[ 5 ], _hash[ 6 ], \ - _hash[ 7 ], _hash[ 8 ], _hash[ 9 ], _hash[ 10 ], _hash[ 11 ], _hash[ 12 ], _hash[ 13 ], \ - _hash[ 14 ], _hash[ 15 ], _hash[ 16 ], _hash[ 17 ], _hash[ 18 ], _hash[ 19 ], _hash[ 20 ], \ - _hash[ 21 ], _hash[ 22 ], _hash[ 23 ], _hash[ 24 ], _hash[ 25 ], _hash[ 26 ], _hash[ 27 ], \ - _hash[ 28 ], _hash[ 29 ], _hash[ 30 ], _hash[ 31 ] diff --git a/src/server.c b/src/server.c index a246fe5..0e01683 100644 --- a/src/server.c +++ b/src/server.c @@ -544,11 +544,21 @@ void paste_server( if (!audit_result.capabilities.can_store && audit_result.major_version == 0 && audit_result.minor_version == 0) { // invalid - reply(2, (byte[ 2 ]) { error, 0 }); + byte* err_reply = malloc(2); + if (err_reply) { + err_reply[ 0 ] = error; + err_reply[ 1 ] = 0; + } + reply(2, err_reply); break; } - reply(2, (byte[ 2 ]) { register_neighbor_result, add_neighbor(neighbor) }); + byte* __reply = malloc(2); + if (__reply) { + __reply[ 0 ] = register_neighbor_result; + __reply[ 1 ] = add_neighbor(neighbor); + } + reply(2, __reply); } break; default: { |
