summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile6
-rw-r--r--client/client.c528
-rw-r--r--client/client.h200
-rw-r--r--client/example.c102
-rw-r--r--src/client.c440
-rw-r--r--src/server.c14
6 files changed, 845 insertions, 445 deletions
diff --git a/Makefile b/Makefile
index df73868..549f1d7 100644
--- a/Makefile
+++ b/Makefile
@@ -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: {