summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Moon Flower <moon@coffin.ir> 2025-09-12 18:30:46 +0330
committerGravatar Moon Flower <moon@coffin.ir> 2025-09-12 18:30:46 +0330
commit56cff94ae4a5644fa3f4267f6b5e8cf8e23da468 (patch)
tree6123962693e5519ad4d0c01fe041e58e07c19bec
starting commit
-rw-r--r--.gitignore3
-rw-r--r--Makefile19
-rw-r--r--client.c465
-rw-r--r--common.c424
-rw-r--r--firehose.c94
-rw-r--r--omen.c467
-rw-r--r--packet_storage.c236
-rw-r--r--security.c161
-rw-r--r--server.c752
-rw-r--r--sha256.c104
10 files changed, 2725 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aff9426
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+bin/
+neighbors.bin
+packets/ \ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..fb50ec3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+clean:
+ rm -rf bin/*
+ mkdir bin 2> /dev/null || true
+
+client:
+ clang client.c -o bin/client -lsodium -O3 -g
+
+server:
+ clang server.c -o bin/server -lsodium -O3 -g
+
+release: clean
+ clang client.c -o bin/client -O3
+ clang server.c -o bin/server -O3
+
+crun:
+ bin/client
+
+srun:
+ bin/server \ No newline at end of file
diff --git a/client.c b/client.c
new file mode 100644
index 0000000..6dd9f00
--- /dev/null
+++ b/client.c
@@ -0,0 +1,465 @@
+#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_info run_server_audit(const char* ip, int port) {
+ byte data[] = { audit_server, 0, 0, 0, 0, 0, 0, 0 };
+ byte* response = NULL;
+ size_t resp_len = 0;
+ bool success;
+
+ 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 (struct relay_info) { 0 };
+ }
+
+ struct relay_info info = s2relay_info(resp_len - 1, response + 1, &success);
+ if (!success) { printf("error: failed to deserialize relay_info\n"); }
+
+ free(response);
+ return info;
+}
+
+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(100);
+ 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(100);
+ 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(100);
+ }
+}
+
+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);
+
+ FILE* temp_file = fopen("test_packet.txt", "w");
+ if (temp_file) {
+ fprintf(temp_file, "this is a test message from the moonpaste client.");
+ fclose(temp_file);
+ }
+
+ keypair author = generate_keypair();
+
+ struct data_packet new_packet = create_packet("test_packet.txt", author, 0, NULL);
+ 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);
+
+ remove("test_packet.txt");
+
+ 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/common.c b/common.c
new file mode 100644
index 0000000..183cfb2
--- /dev/null
+++ b/common.c
@@ -0,0 +1,424 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#define likely(A) __builtin_expect(A, 1)
+#define unlikely(A) __builtin_expect(A, 0)
+
+typedef uint8_t byte;
+
+typedef byte ipv4[ 4 ];
+typedef byte ipv6[ 16 ];
+
+typedef byte hash[ 32 ];
+typedef byte public_key[ 32 ];
+typedef byte private_key[ 32 ];
+typedef byte signature[ 64 ];
+
+const uint16_t default_port = 8040;
+
+enum client_input {
+ // ask server for its information (responds with audit_response)
+ audit_server = 0x9a,
+
+ // ask server for a random set of neighbors
+ // (input uint8_t neighbor_count, responds with neighbors_info)
+ get_neighbors = 0x20,
+
+ // monitor incoming packets
+ // (input struct firehose_request, responds with firehose_reply)
+ firehose = 0xf1,
+
+ // give a packet to the relay
+ // (input struct data_packet, responds with push_packet_result)
+ push_packet = 0x88,
+
+ // get a packet from the server
+ // (input hash, responds with get_packet_result)
+ get_packet = 0x87,
+
+ // register a new neighbor relay to this server
+ // (input struct relay_neighbor, responds with register_neighbor_result)
+ register_neighbor = 0x73,
+};
+
+enum server_reply {
+ // pretty self-explanatory.
+ error = 0x1,
+
+ // type `struct relay_info`
+ audit_response = 0xaf,
+ // type `struct relay_neighbor[ neighbor_count ]`
+ neighbors_info = 0x43,
+ // type `uint64_t new_offset, struct data_packet[ count ]`
+ firehose_reply = 0x6c,
+ // type `bool result_code`
+ push_packet_result = 0x4a,
+ // type `struct data_packet`
+ get_packet_result = 0x42,
+ // type `bool result_code`
+ register_neighbor_result = 0x6e,
+};
+
+typedef uint8_t get_neighbors_request;
+
+struct firehose_request {
+ uint8_t count;
+ uint64_t offset;
+};
+
+typedef uint64_t firehose_reply_new_offset;
+
+struct relay_neighbor {
+ bool active; // set to 0 to indicate empty quick connect slot
+
+ bool has_ipv4; // whether the neighbor supports ipv4 connection
+ bool has_ipv6; // whether the neighbor supports ipv6 connection
+ ipv4 v4_addr; // ipv4 address of the neighbor (if supported)
+ ipv6 v6_addr; // ipv6 address of the neighbor (if supported)
+};
+
+struct relay_info {
+ // -- version info --
+ byte major_version;
+ byte minor_version;
+ // -- motd --
+ byte motd[ 128 ];
+ // -- capabilities --
+ struct {
+ uint16_t retention_hours; // how many hours packets will be retained for
+ bool can_store; // whether or not the relay is accepting new packets
+ byte leading_zeros; // relay challenge level
+ bool accepts_plain; // whether or not the relay accepts unencrypted packets
+ } capabilities;
+ // -- statistics --
+ struct {
+ uint16_t neighbors; // current neighboring relay count for this relay
+ uint64_t held_packets; // how many packets the relay is holding
+ struct relay_neighbor quick_connect[ 4 ]; // four best neighbors for quick connection
+ } statistics;
+};
+
+// do not change
+#define MAX_RECIPIENTS 32
+
+struct data_packet {
+ // nonce + hash for PoW
+ uint32_t nonce;
+ hash hash;
+
+ // content inside to be nonce + hashed
+ struct {
+ bool encrypted; // whether the contents is encrypted or plaintext
+ uint64_t created_time_ms; // unix timestamp for creation time in milliseconds
+ public_key author; // message author
+ public_key author_signing_pub; // message signing pubkey
+ uint8_t recipient_count; // number of recipients. leave as 0 for public.
+ public_key recipients[ 32 ]; // up to 32 recipients for packet
+ byte data[ 65536 ]; // packet data
+ signature signature; // data signature
+ } contents;
+};
+
+const size_t CONTENTS_SIZE = 1 + 8 + 32 + 32 + 1 + (32 * MAX_RECIPIENTS) + 65536 + 64;
+const size_t PACKET_SIZE = 4 + 32 + (CONTENTS_SIZE);
+
+struct relay_neighbor s2relay_neighbor(size_t len, byte *stream, bool *success) {
+ if (len != 1 + 1 + 1 + 4 + 16) {
+ *success = false;
+ return (struct relay_neighbor) { 0 };
+ }
+
+ struct relay_neighbor out;
+ out.active = stream[ 0 ] != 0;
+ out.has_ipv4 = stream[ 1 ] != 0;
+ out.has_ipv6 = stream[ 2 ] != 0;
+
+ memcpy(out.v4_addr, stream + 3, 4);
+ memcpy(out.v6_addr, stream + 7, 16);
+
+ *success = true;
+ return out;
+}
+
+void relay_neighbor2s(struct relay_neighbor data, byte *stream) {
+ stream[ 0 ] = data.active ? 1 : 0;
+ stream[ 1 ] = data.has_ipv4 ? 1 : 0;
+ stream[ 2 ] = data.has_ipv6 ? 1 : 0;
+
+ memcpy(stream + 3, data.v4_addr, 4);
+ memcpy(stream + 7, data.v6_addr, 16);
+}
+
+struct relay_info s2relay_info(size_t len, byte *stream, bool *success) {
+ if (len != 1 + 1 + 128 + 2 + 1 + 1 + 1 + 2 + 8 + (4 * (1 + 1 + 1 + 4 + 16))) {
+ *success = false;
+ return (struct relay_info) { 0 };
+ }
+
+ struct relay_info out;
+ size_t off = 0;
+
+ out.major_version = stream[ off++ ];
+ out.minor_version = stream[ off++ ];
+
+ memcpy(out.motd, stream + off, 128);
+ off += 128;
+
+ out.capabilities.retention_hours = stream[ off ] + (stream[ off + 1 ] << 8);
+ off += 2;
+ out.capabilities.can_store = stream[ off++ ] != 0;
+ out.capabilities.leading_zeros = stream[ off++ ];
+ out.capabilities.accepts_plain = stream[ off++ ] != 0;
+
+ out.statistics.neighbors = stream[ off ] + (stream[ off + 1 ] << 8);
+ off += 2;
+ out.statistics.held_packets
+ = ((uint64_t) stream[ off ]) | ((uint64_t) stream[ off + 1 ] << 8)
+ | ((uint64_t) stream[ off + 2 ] << 16) | ((uint64_t) stream[ off + 3 ] << 24)
+ | ((uint64_t) stream[ off + 4 ] << 32) | ((uint64_t) stream[ off + 5 ] << 40)
+ | ((uint64_t) stream[ off + 6 ] << 48) | ((uint64_t) stream[ off + 7 ] << 56);
+ off += 8;
+
+ for (int i = 0; i < 4; i++) {
+ bool ok;
+ out.statistics.quick_connect[ i ] = s2relay_neighbor(1 + 1 + 1 + 4 + 16, stream + off, &ok);
+ if (!ok) {
+ *success = false;
+ return (struct relay_info) { 0 };
+ }
+ off += 1 + 1 + 1 + 4 + 16;
+ }
+
+ *success = true;
+ return out;
+}
+
+void relay_info2s(struct relay_info data, byte *stream) {
+ size_t off = 0;
+
+ stream[ off++ ] = data.major_version;
+ stream[ off++ ] = data.minor_version;
+
+ memcpy(stream + off, data.motd, 128);
+ off += 128;
+
+ stream[ off++ ] = data.capabilities.retention_hours & 0xFF;
+ stream[ off++ ] = (data.capabilities.retention_hours >> 8) & 0xFF;
+ stream[ off++ ] = data.capabilities.can_store ? 1 : 0;
+ stream[ off++ ] = data.capabilities.leading_zeros;
+ stream[ off++ ] = data.capabilities.accepts_plain ? 1 : 0;
+
+ stream[ off++ ] = data.statistics.neighbors & 0xFF;
+ stream[ off++ ] = (data.statistics.neighbors >> 8) & 0xFF;
+
+ stream[ off++ ] = data.statistics.held_packets & 0xFF;
+ stream[ off++ ] = (data.statistics.held_packets >> 8) & 0xFF;
+ stream[ off++ ] = (data.statistics.held_packets >> 16) & 0xFF;
+ stream[ off++ ] = (data.statistics.held_packets >> 24) & 0xFF;
+ stream[ off++ ] = (data.statistics.held_packets >> 32) & 0xFF;
+ stream[ off++ ] = (data.statistics.held_packets >> 40) & 0xFF;
+ stream[ off++ ] = (data.statistics.held_packets >> 48) & 0xFF;
+ stream[ off++ ] = (data.statistics.held_packets >> 56) & 0xFF;
+
+ for (int i = 0; i < 4; i++) {
+ relay_neighbor2s(data.statistics.quick_connect[ i ], stream + off);
+ off += 1 + 1 + 1 + 4 + 16;
+ }
+}
+
+struct data_packet s2data_packet(size_t len, byte *stream, bool *success) {
+ *success = false;
+ struct data_packet out = { 0 };
+ if (len != PACKET_SIZE) {
+ printf("s2data_packet: length mismatch. expected %zu, got %zu\n", PACKET_SIZE, len);
+ return out;
+ }
+
+ size_t off = 0;
+
+ out.nonce = stream[ off ] | (stream[ off + 1 ] << 8) | (stream[ off + 2 ] << 16)
+ | (stream[ off + 3 ] << 24);
+ off += 4;
+
+ memcpy(out.hash, stream + off, 32);
+ off += 32;
+
+ out.contents.encrypted = stream[ off++ ] != 0;
+
+ out.contents.created_time_ms
+ = ((uint64_t) stream[ off ]) | ((uint64_t) stream[ off + 1 ] << 8)
+ | ((uint64_t) stream[ off + 2 ] << 16) | ((uint64_t) stream[ off + 3 ] << 24)
+ | ((uint64_t) stream[ off + 4 ] << 32) | ((uint64_t) stream[ off + 5 ] << 40)
+ | ((uint64_t) stream[ off + 6 ] << 48) | ((uint64_t) stream[ off + 7 ] << 56);
+ off += 8;
+
+ memcpy(out.contents.author, stream + off, 32);
+ off += 32;
+
+ memcpy(out.contents.author_signing_pub, stream + off, 32);
+ off += 32;
+
+ out.contents.recipient_count = stream[ off++ ];
+ memcpy(out.contents.recipients, stream + off, 32 * MAX_RECIPIENTS);
+ off += 32 * MAX_RECIPIENTS;
+
+ memcpy(out.contents.data, stream + off, 65536);
+ off += 65536;
+
+ memcpy(out.contents.signature, stream + off, 64);
+ off += 64;
+
+ *success = true;
+ return out;
+}
+
+bool data_packet2s(struct data_packet data, byte *stream) {
+ if (data.contents.recipient_count > MAX_RECIPIENTS) { return false; }
+
+ size_t off = 0;
+
+ stream[ off++ ] = data.nonce & 0xFF;
+ stream[ off++ ] = (data.nonce >> 8) & 0xFF;
+ stream[ off++ ] = (data.nonce >> 16) & 0xFF;
+ stream[ off++ ] = (data.nonce >> 24) & 0xFF;
+
+ memcpy(stream + off, data.hash, 32);
+ off += 32;
+
+ stream[ off++ ] = data.contents.encrypted ? 1 : 0;
+
+ for (int i = 0; i < 8; i++) stream[ off++ ] = (data.contents.created_time_ms >> (i * 8)) & 0xFF;
+
+ memcpy(stream + off, data.contents.author, 32);
+ off += 32;
+
+ memcpy(stream + off, data.contents.author_signing_pub, 32);
+ off += 32;
+
+ stream[ off++ ] = data.contents.recipient_count;
+ memcpy(stream + off, data.contents.recipients, 32 * MAX_RECIPIENTS);
+ off += 32 * MAX_RECIPIENTS;
+
+ memcpy(stream + off, data.contents.data, 65536);
+ off += 65536;
+
+ memcpy(stream + off, data.contents.signature, 64);
+ off += 64;
+
+ if (off != PACKET_SIZE) {
+ printf("data_packet2s: size mismatch wrote %zu expected %zu\n", off, PACKET_SIZE);
+ return false;
+ }
+
+ return true;
+}
+
+get_neighbors_request s2get_neighbors_request(size_t len, byte *stream, bool *success) {
+ if (len != 1) {
+ *success = false;
+ return 0;
+ }
+ *success = true;
+ return stream[ 0 ];
+}
+
+void get_neighbors_request2s(get_neighbors_request data, byte *stream) { stream[ 0 ] = data; }
+
+struct firehose_request s2firehose_request(size_t len, byte *stream, bool *success) {
+ if (len != 1 + 8) {
+ *success = false;
+ return (struct firehose_request) { 0 };
+ }
+ struct firehose_request out;
+ out.count = stream[ 0 ];
+ out.offset = ((uint64_t) stream[ 1 ]) | ((uint64_t) stream[ 2 ] << 8)
+ | ((uint64_t) stream[ 3 ] << 16) | ((uint64_t) stream[ 4 ] << 24)
+ | ((uint64_t) stream[ 5 ] << 32) | ((uint64_t) stream[ 6 ] << 40)
+ | ((uint64_t) stream[ 7 ] << 48) | ((uint64_t) stream[ 8 ] << 56);
+ *success = true;
+ return out;
+}
+
+void firehose_request2s(struct firehose_request data, byte *stream) {
+ stream[ 0 ] = data.count;
+ stream[ 1 ] = data.offset & 0xFF;
+ stream[ 2 ] = (data.offset >> 8) & 0xFF;
+ stream[ 3 ] = (data.offset >> 16) & 0xFF;
+ stream[ 4 ] = (data.offset >> 24) & 0xFF;
+ stream[ 5 ] = (data.offset >> 32) & 0xFF;
+ stream[ 6 ] = (data.offset >> 40) & 0xFF;
+ stream[ 7 ] = (data.offset >> 48) & 0xFF;
+ stream[ 8 ] = (data.offset >> 56) & 0xFF;
+}
+
+void s2hash(size_t len, byte *stream, bool *success, hash out) {
+ if (len != 32) {
+ *success = false;
+ return;
+ }
+ memcpy(out, stream, 32);
+ *success = true;
+}
+
+void hash2s(hash data, byte *stream) { memcpy(stream, data, 32); }
+
+bool check_leading_zeros(const hash _hash, byte required_zeros) {
+ if (required_zeros == 0) return true;
+
+ int full_bytes = required_zeros / 8;
+ int remaining_bits = required_zeros % 8;
+
+ for (int i = 0; i < full_bytes; i++) {
+ if (_hash[ i ] != 0) return false;
+ }
+
+ if (remaining_bits > 0) {
+ byte mask = (0xFF << (8 - remaining_bits));
+ if ((_hash[ full_bytes ] & mask) != 0) return false;
+ }
+
+ return true;
+}
+
+uint64_t get_current_time_ms() {
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ return (uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000;
+}
+
+#define RELAY_NEIGHBOR_WIRE_LEN (1 + 1 + 1 + 4 + 16)
+
+static inline uint64_t read_u64_le(const byte *b) {
+ uint64_t v = 0;
+ for (int i = 0; i < 8; ++i) v |= (uint64_t) b[ i ] << (8 * i);
+ return v;
+}
+
+static inline void write_u64_le(byte *b, uint64_t v) {
+ for (int i = 0; i < 8; ++i) b[ i ] = (v >> (8 * i)) & 0xFF;
+}
+
+static inline uint32_t read_u32_le(const byte *b) {
+ uint32_t v = 0;
+ for (int i = 0; i < 4; ++i) v |= (uint32_t) b[ i ] << (8 * i);
+ return v;
+}
+
+static inline void write_u32_le(byte *b, uint32_t v) {
+ for (int i = 0; i < 4; ++i) b[ i ] = (v >> (8 * i)) & 0xFF;
+}
+
+static inline uint16_t read_u16_le(const byte *b) {
+ return (uint16_t) b[ 0 ] | ((uint16_t) b[ 1 ] << 8);
+}
+
+static inline void write_u16_le(byte *b, uint16_t v) {
+ b[ 0 ] = v & 0xFF;
+ b[ 1 ] = (v >> 8) & 0xFF;
+} \ No newline at end of file
diff --git a/firehose.c b/firehose.c
new file mode 100644
index 0000000..240dfd8
--- /dev/null
+++ b/firehose.c
@@ -0,0 +1,94 @@
+#include "common.c"
+#include <pthread.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#define FIREHOSE_BUFFER_SIZE 1 << 14
+
+typedef struct {
+ hash packet_hash;
+ uint64_t storage_index;
+} water_splash;
+
+typedef struct {
+ water_splash entries[ FIREHOSE_BUFFER_SIZE ];
+ uint16_t head;
+ uint16_t tail;
+ uint16_t count;
+ uint64_t total_packets;
+ pthread_mutex_t mutex;
+} fire_buffer;
+
+static fire_buffer firehose_buffer;
+
+void firehose_init();
+void firehose_push(hash new_hash, uint64_t storage_index);
+uint16_t firehose_get_count();
+uint64_t firehose_get_total_packets();
+uint16_t firehose_get_packets(uint64_t offset, uint16_t count, water_splash* output_entries);
+
+void firehose_init() {
+ memset(firehose_buffer.entries, 0, sizeof(firehose_buffer.entries));
+ firehose_buffer.head = 0;
+ firehose_buffer.tail = 0;
+ firehose_buffer.count = 0;
+ firehose_buffer.total_packets = 0;
+ pthread_mutex_init(&firehose_buffer.mutex, NULL);
+}
+
+uint64_t current_base_offset = 0;
+
+void firehose_push(hash new_hash, uint64_t storage_index) {
+ pthread_mutex_lock(&firehose_buffer.mutex);
+
+ memcpy(firehose_buffer.entries[ firehose_buffer.tail ].packet_hash, new_hash, sizeof(hash));
+ firehose_buffer.entries[ firehose_buffer.tail ].storage_index = storage_index;
+ if (firehose_buffer.count == FIREHOSE_BUFFER_SIZE) {
+ firehose_buffer.head = (firehose_buffer.head + 1) % FIREHOSE_BUFFER_SIZE;
+ current_base_offset++;
+ } else {
+ firehose_buffer.count++;
+ }
+ firehose_buffer.tail = (firehose_buffer.tail + 1) % FIREHOSE_BUFFER_SIZE;
+
+ firehose_buffer.total_packets++;
+
+ pthread_mutex_unlock(&firehose_buffer.mutex);
+}
+
+uint16_t firehose_get_count() { return firehose_buffer.count; }
+uint64_t firehose_get_total_packets() { return firehose_buffer.total_packets; }
+
+uint16_t firehose_get_packets(uint64_t offset, uint16_t count, water_splash* output_entries) {
+ pthread_mutex_lock(&firehose_buffer.mutex);
+
+ uint16_t copied_count = 0;
+ uint64_t start_offset = firehose_buffer.total_packets - firehose_buffer.count;
+
+ if (offset < start_offset || offset >= firehose_buffer.total_packets) {
+ pthread_mutex_unlock(&firehose_buffer.mutex);
+ return 0;
+ }
+
+ uint64_t relative_offset = offset - current_base_offset;
+ if (offset < current_base_offset || relative_offset >= firehose_buffer.count) {
+ pthread_mutex_unlock(&firehose_buffer.mutex);
+ return 0; // offset too old or beyond current entries
+ }
+ uint16_t start_index = (firehose_buffer.head + relative_offset) % FIREHOSE_BUFFER_SIZE;
+
+ for (int i = 0; i < count; i++) {
+ if (relative_offset + i >= firehose_buffer.count) { break; }
+
+ uint16_t current_index = (start_index + i) % FIREHOSE_BUFFER_SIZE;
+ memcpy(
+ &output_entries[ i ], &firehose_buffer.entries[ current_index ], sizeof(water_splash));
+ copied_count++;
+ }
+
+ pthread_mutex_unlock(&firehose_buffer.mutex);
+ return copied_count;
+}
diff --git a/omen.c b/omen.c
new file mode 100644
index 0000000..2f60a8c
--- /dev/null
+++ b/omen.c
@@ -0,0 +1,467 @@
+#pragma once
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/socket.h>
+#include <unistd.h>
+//
+#include "common.c"
+
+typedef uint8_t byte;
+typedef void (*tome_ptr)(
+ char *ip_addr, size_t addr_size, byte *in, size_t in_len, byte **out, size_t *out_len);
+struct Task {
+ int fd;
+ struct Task *next;
+};
+
+#define MAX_BUFFER 1 << 18
+#define MAX_BODY 1 << 17
+#define MAX_CONNECTIONS 2048
+
+// vars
+
+int worker_count = 16;
+
+pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;
+struct Task *head = NULL;
+struct Task *tail = NULL;
+size_t queue_size = 0;
+tome_ptr global_handler = NULL;
+
+// utils
+
+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) continue;
+ if (errno == EAGAIN || errno == EWOULDBLOCK) continue;
+ return -1;
+ }
+ off += (size_t) r;
+ }
+ return (ssize_t) off;
+}
+
+static int 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 -1;
+ while (1) {
+ if (cap - len < 1024) {
+ size_t ncap = cap * 2;
+ char *n = realloc(buf, ncap);
+ if (!n) {
+ free(buf);
+ return -1;
+ }
+ buf = n;
+ cap = ncap;
+ }
+ ssize_t r = recv(fd, buf + len, cap - len, 0);
+ if (r < 0) {
+ if (errno == EINTR) continue;
+ free(buf);
+ return -1;
+ } else if (r == 0) {
+ len = len;
+ break;
+ }
+ len += (size_t) r;
+ if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4) != NULL) break;
+ if (len > MAX_BUFFER) break;
+ }
+ *out_buf = buf;
+ *out_len = len;
+ return 0;
+}
+
+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 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;
+ size_t line_len = (size_t) (line_end - p);
+ if (line_len >= 15) {
+ if (strncasecmp(p, "Content-Length:", 15) == 0) {
+ const char *num = p + 15;
+ while (num < line_end && (*num == ' ' || *num == '\t')) num++;
+ char tmp[ 32 ] = { 0 };
+ size_t to_copy = line_len - (num - p);
+ if (to_copy >= sizeof(tmp)) to_copy = sizeof(tmp) - 1;
+ memcpy(tmp, num, to_copy);
+ for (int i = (int) strlen(tmp) - 1; i >= 0
+ && (tmp[ i ] == '\r' || tmp[ i ] == '\n'
+ || tmp[ i ] == ' ' || tmp[ i ] == '\t');
+ --i)
+ tmp[ i ] = 0;
+ long v = strtol(tmp, NULL, 10);
+ if (v < 0) return -1;
+ return (ssize_t) v;
+ }
+ }
+ p = line_end + 1;
+ }
+ return -1;
+}
+
+static int send_http_response(int fd, const byte *payload, size_t payload_len) {
+ const char *template_hdr
+ = "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n";
+ char hdr[ 128 ];
+ int n = snprintf(hdr, sizeof(hdr), template_hdr, payload_len);
+ if (n < 0 || (size_t) n >= sizeof(hdr)) return -1;
+ if (write_all(fd, hdr, (size_t) n) < 0) return -1;
+ if (payload_len > 0 && payload) {
+ if (write_all(fd, payload, payload_len) < 0) return -1;
+ }
+ return 0;
+}
+
+// server
+
+void *omen_bound(void *arg) {
+ (void) arg;
+ while (1) {
+ int client_fd = -1;
+ struct sockaddr_in client_addr;
+ socklen_t client_addr_len = sizeof(client_addr);
+
+ pthread_mutex_lock(&queue_mutex);
+ while (head == NULL) { pthread_cond_wait(&queue_cond, &queue_mutex); }
+ struct Task *task = head;
+ head = head->next;
+ if (head == NULL) tail = NULL;
+ queue_size--;
+ client_fd = task->fd;
+
+ if (getpeername(client_fd, (struct sockaddr *) &client_addr, &client_addr_len) < 0) {
+ memset(&client_addr, 0, sizeof(client_addr));
+ }
+
+ free(task);
+ pthread_mutex_unlock(&queue_mutex);
+
+ char client_ip[ INET_ADDRSTRLEN ] = { 0 };
+ if (client_addr.sin_family == AF_INET) {
+ inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
+ }
+
+ struct timeval timeout = { 5, 0 };
+ setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
+
+ char *hdr_buf = NULL;
+ size_t hdr_len = 0;
+ if (read_until_headers(client_fd, &hdr_buf, &hdr_len) < 0) {
+ close(client_fd);
+ free(hdr_buf);
+ continue;
+ }
+
+ char *sep = memmem(hdr_buf, hdr_len, "\r\n\r\n", 4);
+ size_t body_len = 0;
+ byte *body_ptr = NULL;
+
+ if (sep) {
+ size_t headers_bytes = (size_t) (sep - hdr_buf) + 4;
+ ssize_t cl = parse_content_length(hdr_buf, headers_bytes);
+ if (cl > 0) {
+ size_t already = hdr_len - headers_bytes;
+ if ((size_t) cl > MAX_BODY) cl = MAX_BODY;
+ body_ptr = malloc((size_t) cl);
+ if (!body_ptr) {
+ free(hdr_buf);
+ close(client_fd);
+ continue;
+ }
+ if (already > 0) {
+ size_t copy = already;
+ if (copy > (size_t) cl) copy = (size_t) cl;
+ memcpy(body_ptr, sep + 4, copy);
+ body_len = copy;
+ }
+ if ((size_t) body_len < (size_t) cl) {
+ if (read_exact(client_fd, body_ptr + body_len, (size_t) cl - body_len) < 0) {
+ free(body_ptr);
+ free(hdr_buf);
+ close(client_fd);
+ continue;
+ }
+ body_len = (size_t) cl;
+ }
+ } else {
+ size_t already = hdr_len - (size_t) (sep - hdr_buf) - 4;
+ if (already > 0) {
+ if (already > MAX_BODY) already = MAX_BODY;
+ body_ptr = malloc(already);
+ if (body_ptr) {
+ memcpy(body_ptr, sep + 4, already);
+ body_len = already;
+ }
+ } else {
+ body_ptr = NULL;
+ body_len = 0;
+ }
+ }
+ } else {
+ size_t use = hdr_len;
+ if (use > MAX_BODY) use = MAX_BODY;
+ body_ptr = malloc(use);
+ if (body_ptr) {
+ memcpy(body_ptr, hdr_buf, use);
+ body_len = use;
+ }
+ }
+
+ byte *out = NULL;
+ size_t out_len = 0;
+
+ if (global_handler)
+ global_handler(client_ip, sizeof(client_ip), body_ptr, body_len, &out, &out_len);
+ if (out_len > MAX_BODY) out_len = MAX_BODY;
+
+ send_http_response(client_fd, out, out_len);
+
+ free(out);
+ free(body_ptr);
+ free(hdr_buf);
+ close(client_fd);
+ }
+ return NULL;
+}
+
+int omen(int port, tome_ptr handler) {
+ global_handler = handler;
+ signal(SIGPIPE, SIG_IGN);
+
+ int server_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (server_fd < 0) return -1;
+
+ int opt = 1;
+ setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+ struct sockaddr_in addr = { 0 };
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = htons(port);
+
+ if (bind(server_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0
+ || listen(server_fd, MAX_CONNECTIONS) < 0) {
+ close(server_fd);
+ return -1;
+ }
+
+ pthread_t *threads = malloc((size_t) worker_count * sizeof(pthread_t));
+ if (!threads) {
+ close(server_fd);
+ return -1;
+ }
+
+ for (int i = 0; i < worker_count; i++) {
+ if (pthread_create(&threads[ i ], NULL, omen_bound, NULL) != 0) {
+ perror("pthread_create failed");
+ } else {
+ pthread_detach(threads[ i ]);
+ }
+ }
+
+ while (1) {
+ struct sockaddr_in client_addr;
+ socklen_t len = sizeof(client_addr);
+ int client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &len);
+ if (client_fd < 0) continue;
+
+ pthread_mutex_lock(&queue_mutex);
+ if (queue_size >= (size_t) MAX_CONNECTIONS) {
+ // note: The server converts into a teapot if the number of concurrent connections
+ // reaches the max amount. this can be mitigated by disconnecting some clients,
+ // which will turn the teapot back to an actual server.
+ char err_resp[] = "HTTP/1.1 418 I'm a teapot\r\n"
+ "Content-Length: 24\r\n"
+ "Connection: close\r\n\r\n"
+ "Max connections reached";
+ write_all(client_fd, err_resp, 92);
+ close(client_fd);
+ pthread_mutex_unlock(&queue_mutex);
+ continue;
+ }
+
+ struct Task *new_task = malloc(sizeof(struct Task));
+ if (!new_task) {
+ close(client_fd);
+ pthread_mutex_unlock(&queue_mutex);
+ continue;
+ }
+ new_task->fd = client_fd;
+ new_task->next = NULL;
+ if (tail) tail->next = new_task;
+ else
+ head = new_task;
+ tail = new_task;
+ queue_size++;
+ pthread_cond_signal(&queue_cond);
+ pthread_mutex_unlock(&queue_mutex);
+ }
+
+ close(server_fd);
+ return 0;
+}
+
+// client
+
+int study(
+ const char *ip,
+ int port,
+ const byte *data,
+ size_t data_len,
+ byte **response,
+ size_t *resp_len) {
+ int sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock < 0) return -1;
+
+ struct sockaddr_in addr = { 0 };
+ addr.sin_family = AF_INET;
+ if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
+ close(sock);
+ return -1;
+ }
+ addr.sin_port = htons(port);
+
+ 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, sizeof(addr)) < 0) {
+ close(sock);
+ return -1;
+ }
+
+ if (data_len > MAX_BODY) data_len = MAX_BODY;
+ char req_hdr[ 256 ];
+ int hdr_n = snprintf(
+ req_hdr,
+ sizeof(req_hdr),
+ "POST /data HTTP/1.1\r\nHost: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n",
+ ip,
+ data_len);
+ if (hdr_n < 0 || (size_t) hdr_n >= sizeof(req_hdr)) {
+ close(sock);
+ return -1;
+ }
+
+ if (write_all(sock, req_hdr, (size_t) hdr_n) < 0
+ || (data_len > 0 && write_all(sock, data, data_len) < 0)) {
+ close(sock);
+ return -1;
+ }
+
+ char *hdr_buf = NULL;
+ size_t hdr_len = 0;
+ if (read_until_headers(sock, &hdr_buf, &hdr_len) < 0) {
+ close(sock);
+ free(hdr_buf);
+ return -1;
+ }
+
+ if (memmem(hdr_buf, hdr_len, "HTTP/1.1 418 I'm a teapot", 34) != NULL) {
+ close(sock);
+ free(hdr_buf);
+ return -2;
+ }
+
+ char *sep = memmem(hdr_buf, hdr_len, "\r\n\r\n", 4);
+ size_t body_len = 0;
+ byte *body = NULL;
+ 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 = malloc((size_t) cl);
+ if (!body) {
+ free(hdr_buf);
+ close(sock);
+ return -1;
+ }
+ if (already > 0) {
+ size_t copy = already;
+ if (copy > (size_t) cl) copy = (size_t) cl;
+ memcpy(body, sep + 4, copy);
+ body_len = copy;
+ }
+ if ((size_t) body_len < (size_t) cl) {
+ if (read_exact(sock, body + body_len, (size_t) cl - body_len) < 0) {
+ free(body);
+ free(hdr_buf);
+ close(sock);
+ return -1;
+ }
+ body_len = (size_t) cl;
+ }
+ } else {
+ if (already > 0) {
+ if (already > MAX_BODY) already = MAX_BODY;
+ body = malloc(already);
+ if (body) {
+ memcpy(body, sep + 4, already);
+ body_len = already;
+ }
+ } else {
+ body = NULL;
+ body_len = 0;
+ }
+ }
+ } else {
+ size_t use = hdr_len;
+ if (use > MAX_BODY) use = MAX_BODY;
+ body = malloc(use);
+ if (body) {
+ memcpy(body, hdr_buf, use);
+ body_len = use;
+ }
+ }
+
+ free(hdr_buf);
+ close(sock);
+
+ if (body_len > 0 && body) {
+ *response = body;
+ *resp_len = body_len;
+ } else {
+ *response = NULL;
+ *resp_len = 0;
+ free(body);
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/packet_storage.c b/packet_storage.c
new file mode 100644
index 0000000..e618490
--- /dev/null
+++ b/packet_storage.c
@@ -0,0 +1,236 @@
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+//
+#include "common.c"
+
+const uint64_t max_storage = 1 << 16;
+
+#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 ]
+
+uint64_t current_packet_ptr = 0;
+uint64_t packet_count = 0;
+struct data_packet_entry {
+ struct data_packet packet;
+ uint64_t next;
+ uint64_t prev;
+ bool is_full;
+}* packet_storage;
+
+// TODO: yes, i know it's weird, but this has to be zero
+#define minus_one (uint64_t) 0
+
+pthread_mutex_t packet_storage_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+struct data_packet* load_packet(const hash packet_hash) {
+ char filename[ 128 ];
+ sprintf(filename, "packets/" hash_fmt ".bin", hash_ref(packet_hash));
+
+ FILE* file = fopen(filename, "rb");
+ if (!file) { return NULL; }
+
+ fseek(file, 0, SEEK_END);
+ size_t file_size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ if (file_size != PACKET_SIZE) {
+ fclose(file);
+ return NULL;
+ }
+
+ byte* buffer = malloc(file_size);
+ if (!buffer) {
+ fclose(file);
+ return NULL;
+ }
+
+ size_t r = fread(buffer, 1, file_size, file);
+ fclose(file);
+ if (r != file_size) {
+ free(buffer);
+ return NULL;
+ }
+
+ bool success;
+ struct data_packet* packet = malloc(sizeof(struct data_packet));
+ if (!packet) {
+ free(buffer);
+ return NULL;
+ }
+ *packet = s2data_packet(file_size, buffer, &success);
+ free(buffer);
+
+ if (!success) {
+ free(packet);
+ return NULL;
+ }
+
+ return packet;
+}
+
+uint64_t set_data_packet(struct data_packet* new_packet);
+
+struct data_packet_entry* get_data_packet(hash _hash) {
+ uint64_t hash_val;
+ memcpy(&hash_val, _hash, sizeof(uint64_t));
+ uint64_t cur_index = hash_val % max_storage;
+
+ pthread_mutex_lock(&packet_storage_mutex);
+
+ struct data_packet_entry* current_entry = &packet_storage[ cur_index ];
+ while (cur_index != minus_one) {
+ if (current_entry->is_full
+ && memcmp(current_entry->packet.hash, _hash, sizeof(hash)) == 0) {
+ pthread_mutex_unlock(&packet_storage_mutex);
+ return current_entry;
+ }
+
+ cur_index = current_entry->next;
+ if (cur_index != minus_one) { current_entry = &packet_storage[ cur_index ]; }
+ }
+
+ pthread_mutex_unlock(&packet_storage_mutex);
+ struct data_packet* packet = load_packet(_hash);
+
+ if (packet) {
+ uint64_t add_idx = set_data_packet(packet);
+
+ if (add_idx != minus_one) {
+ return &packet_storage[ add_idx ];
+ } else {
+ static struct data_packet_entry temp_entry;
+ temp_entry.packet = *packet;
+ return &temp_entry;
+ }
+ }
+
+ return NULL;
+}
+
+uint64_t set_data_packet(struct data_packet* new_packet) {
+ pthread_mutex_lock(&packet_storage_mutex);
+
+ uint64_t hash_val;
+ memcpy(&hash_val, new_packet->hash, sizeof(uint64_t));
+ uint64_t start_index = hash_val % max_storage;
+ uint64_t cur_index = start_index;
+
+ struct data_packet_entry* current_entry = &packet_storage[ cur_index ];
+
+ while (true) {
+ if (current_entry->is_full
+ && memcmp(current_entry->packet.hash, new_packet->hash, sizeof(hash)) == 0) {
+ memcpy(&current_entry->packet, new_packet, sizeof(struct data_packet));
+ pthread_mutex_unlock(&packet_storage_mutex);
+ return cur_index;
+ }
+
+ if (current_entry->next == minus_one) break;
+ cur_index = current_entry->next;
+ current_entry = &packet_storage[ cur_index ];
+ }
+
+ if (!current_entry->is_full) {
+ memcpy(&current_entry->packet, new_packet, sizeof(struct data_packet));
+ current_entry->is_full = true;
+ current_entry->next = minus_one;
+ current_entry->prev = minus_one;
+ packet_count++;
+ pthread_mutex_unlock(&packet_storage_mutex);
+ return cur_index;
+ }
+
+ uint64_t probes = 0;
+ uint64_t linear_probe_index = (cur_index + 1) % max_storage;
+
+ while (probes < max_storage) {
+ struct data_packet_entry* empty_slot = &packet_storage[ linear_probe_index ];
+
+ if (!empty_slot->is_full) {
+ memcpy(&empty_slot->packet, new_packet, sizeof(struct data_packet));
+ empty_slot->is_full = true;
+ empty_slot->next = minus_one;
+ empty_slot->prev = cur_index;
+ current_entry->next = linear_probe_index;
+ packet_count++;
+ pthread_mutex_unlock(&packet_storage_mutex);
+ return linear_probe_index;
+ }
+
+ linear_probe_index = (linear_probe_index + 1) % max_storage;
+ probes++;
+ }
+
+ // table full
+ pthread_mutex_unlock(&packet_storage_mutex);
+ return minus_one;
+}
+
+#define overload __attribute__((overloadable))
+
+bool _delete_at_index(uint64_t index);
+
+overload void delete_data_packet(hash _hash) {
+ pthread_mutex_lock(&packet_storage_mutex);
+
+ uint64_t hash_val;
+ memcpy(&hash_val, _hash, sizeof(uint64_t));
+ uint64_t start_index = hash_val % max_storage;
+ uint64_t cur_index = start_index;
+
+ while (cur_index != minus_one) {
+ struct data_packet_entry* current_entry = &packet_storage[ cur_index ];
+ if (current_entry->is_full
+ && memcmp(current_entry->packet.hash, _hash, sizeof(hash)) == 0) {
+ _delete_at_index(cur_index);
+ pthread_mutex_unlock(&packet_storage_mutex);
+ return;
+ }
+
+ if (current_entry->next == minus_one) {
+ pthread_mutex_unlock(&packet_storage_mutex);
+ return;
+ }
+ cur_index = current_entry->next;
+ }
+
+ pthread_mutex_unlock(&packet_storage_mutex);
+}
+
+overload void delete_data_packet(uint64_t index) {
+ pthread_mutex_lock(&packet_storage_mutex);
+ _delete_at_index(index);
+ pthread_mutex_unlock(&packet_storage_mutex);
+}
+
+// caller must already have a lock!
+bool _delete_at_index(uint64_t index) {
+ if (index >= max_storage) return false;
+ struct data_packet_entry* current_entry = &packet_storage[ index ];
+ if (!current_entry->is_full) return false;
+
+ if (current_entry->prev != minus_one) {
+ struct data_packet_entry* prev_entry = &packet_storage[ current_entry->prev ];
+ prev_entry->next = current_entry->next;
+ }
+
+ if (current_entry->next != minus_one) {
+ struct data_packet_entry* next_entry = &packet_storage[ current_entry->next ];
+ next_entry->prev = current_entry->prev;
+ }
+
+ current_entry->is_full = false;
+ current_entry->next = minus_one;
+ current_entry->prev = minus_one;
+
+ return true;
+} \ No newline at end of file
diff --git a/security.c b/security.c
new file mode 100644
index 0000000..79dd2c2
--- /dev/null
+++ b/security.c
@@ -0,0 +1,161 @@
+// dependency: libsodium
+#include <sodium.h>
+#include <string.h>
+//
+#include "common.c"
+
+#if crypto_box_PUBLICKEYBYTES != 32 || crypto_box_SECRETKEYBYTES != 32
+ #error currently, moonpaste requires 32 bytes length for public key and private key each.
+#endif
+
+#if crypto_sign_BYTES != 64
+ #error moonpaste needs 64 bytes length for signature.
+#endif
+
+#ifndef SIGN_PUBLICKEYBYTES
+ #define SIGN_PUBLICKEYBYTES crypto_sign_PUBLICKEYBYTES
+#endif
+#ifndef SIGN_SECRETKEYBYTES
+ #define SIGN_SECRETKEYBYTES crypto_sign_SECRETKEYBYTES
+#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;
+
+typedef struct {
+ bool is_valid;
+ byte data[ 65536 - crypto_box_MACBYTES - crypto_box_NONCEBYTES ];
+} plain_payload;
+
+typedef struct {
+ byte nonce[ crypto_box_NONCEBYTES ];
+ byte encrypted_data[ 65536 - crypto_box_NONCEBYTES ];
+ byte signature[ crypto_sign_BYTES ];
+} secure_payload;
+
+bool has_called_sodium_init = false;
+
+void init_sec() {
+ if (unlikely(!has_called_sodium_init)) {
+ if (sodium_init() == -1) {
+ fprintf(stderr, "sodium init failed\n");
+ exit(1);
+ }
+ has_called_sodium_init = true;
+ }
+}
+
+keypair generate_keypair() {
+ init_sec();
+ keypair new;
+ crypto_box_keypair(new.encryption_pub, new.encryption_priv);
+ crypto_sign_keypair(new.signing_pub, new.signing_priv);
+ return new;
+}
+
+secure_payload
+encrypt_and_sign(plain_payload input, keypair author_keys, public_key target_encryption_public) {
+ init_sec();
+
+ secure_payload output;
+ randombytes_buf(output.nonce, crypto_box_NONCEBYTES);
+
+ if (crypto_box_easy(
+ output.encrypted_data,
+ input.data,
+ sizeof(input.data),
+ output.nonce,
+ target_encryption_public,
+ author_keys.encryption_priv)
+ != 0) {
+ fprintf(stderr, "sodium encryption failed\n");
+ exit(1);
+ }
+
+ byte message_to_sign[ crypto_box_NONCEBYTES + sizeof(output.encrypted_data) ];
+ memcpy(message_to_sign, output.nonce, crypto_box_NONCEBYTES);
+ memcpy(
+ message_to_sign + crypto_box_NONCEBYTES,
+ output.encrypted_data,
+ sizeof(output.encrypted_data));
+
+ if (crypto_sign_detached(
+ output.signature,
+ NULL,
+ message_to_sign,
+ sizeof(message_to_sign),
+ author_keys.signing_priv)
+ != 0) {
+ fprintf(stderr, "sodium signing failed\n");
+ exit(1);
+ }
+
+ return output;
+}
+
+plain_payload decrypt(secure_payload input, keypair target_keys, public_key author_signing_public) {
+ init_sec();
+
+ plain_payload output;
+ output.is_valid = true;
+
+ byte message_to_verify[ crypto_box_NONCEBYTES + sizeof(input.encrypted_data) ];
+ memcpy(message_to_verify, input.nonce, crypto_box_NONCEBYTES);
+ memcpy(
+ message_to_verify + crypto_box_NONCEBYTES,
+ input.encrypted_data,
+ sizeof(input.encrypted_data));
+
+ if (crypto_sign_verify_detached(
+ input.signature, message_to_verify, sizeof(message_to_verify), author_signing_public)
+ != 0) {
+ output.is_valid = false;
+ return output;
+ }
+
+ if (crypto_box_open_easy(
+ output.data,
+ input.encrypted_data,
+ sizeof(input.encrypted_data),
+ input.nonce,
+ target_keys.encryption_pub,
+ target_keys.encryption_priv)
+ != 0) {
+ fprintf(stderr, "sodium decryption failed\n");
+ exit(1);
+ }
+
+ return output;
+}
+
+bool verify_signature(secure_payload input, public_key author_signing_public) {
+ init_sec();
+
+ byte message_to_verify[ crypto_box_NONCEBYTES + sizeof(input.encrypted_data) ];
+ memcpy(message_to_verify, input.nonce, crypto_box_NONCEBYTES);
+ memcpy(
+ message_to_verify + crypto_box_NONCEBYTES,
+ input.encrypted_data,
+ sizeof(input.encrypted_data));
+
+ return crypto_sign_verify_detached(
+ input.signature, message_to_verify, sizeof(message_to_verify), author_signing_public)
+ == 0;
+}
+
+bool verify_raw_signature(
+ const byte* msg,
+ size_t msg_len,
+ const signature signature,
+ const sign_public_key author_signing_public) {
+ init_sec();
+ if (msg == NULL || signature == NULL || author_signing_public == NULL) return false;
+ return crypto_sign_verify_detached(signature, msg, msg_len, author_signing_public) == 0;
+} \ No newline at end of file
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..9c1a710
--- /dev/null
+++ b/server.c
@@ -0,0 +1,752 @@
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+//
+#include "common.c"
+#include "omen.c"
+#include "security.c"
+#include "sha256.c"
+//
+#include "firehose.c"
+#include "packet_storage.c"
+
+pthread_mutex_t log_io_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+enum { _debug = 1, _info = 2, _warn = 3, _error = 4 } log_level = _debug;
+const char* log_level_names[] = { "debug", "info", "warn", "error" };
+#define log(__level, fmt, ...) \
+ ({ \
+ pthread_mutex_lock(&log_io_mutex); \
+ if (__level >= log_level) \
+ fprintf(stderr, "[%s] " fmt, (#__level) + 1 __VA_OPT__(, ) __VA_ARGS__); \
+ pthread_mutex_unlock(&log_io_mutex); \
+ })
+
+const uint64_t max_neighbors = 1 << 10; // probably more than enough for a lot of folks
+
+uint64_t current_neighbor_ptr = 0;
+uint64_t neighbor_count = 0;
+struct relay_neighbor* neighbor_storage;
+bool* empty_neighbor_slots;
+pthread_mutex_t neighbor_storage_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+struct {
+ byte major_version;
+ byte minor_version;
+ byte motd[ 128 ];
+ //
+ uint16_t retention_hours; // should default to 1w (168h)
+ byte leading_zeros; // i recommend 10
+ bool accepts_plain; // for legal reasons
+} server_stats = {
+ .major_version = 0,
+ .minor_version = 1,
+ .motd = "A moonpaste server.", // minecraft much?
+ .retention_hours = 168, // 1 week
+ .leading_zeros = 10, // the moon guarantee
+ .accepts_plain = true, // probably legal
+};
+
+bool add_neighbor(struct relay_neighbor new_neighbor) {
+ log(_info, "adding neighbor\n");
+
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ if (neighbor_count >= max_neighbors) {
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+ return false;
+ }
+
+ uint64_t add_idx = -1;
+ for (uint64_t i = 0; i < max_neighbors; i++) {
+ if (empty_neighbor_slots[ i ]) {
+ add_idx = i;
+ break;
+ }
+ }
+
+ if (add_idx != -1) {
+ neighbor_storage[ add_idx ] = new_neighbor;
+ empty_neighbor_slots[ add_idx ] = false;
+ neighbor_count++;
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+ return true;
+ }
+
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+ return false;
+}
+
+#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 ]
+
+void* propagate_packet(void* _packet) {
+ struct data_packet packet = *(struct data_packet*) _packet;
+ log(_debug, "propagating packet " hash_fmt "\n", hash_ref(packet.hash));
+
+ size_t send_len = 1 + sizeof(struct data_packet);
+ byte* send_data = calloc(send_len, sizeof(byte));
+ send_data[ 0 ] = push_packet;
+ data_packet2s(packet, send_data + 1);
+
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ for (uint64_t i = 0; i < neighbor_count; i++) {
+ struct relay_neighbor neighbor = neighbor_storage[ i ];
+ if (neighbor.active) {
+ byte* response_data = NULL;
+ size_t response_len = 0;
+ int result = -1;
+
+ if (neighbor.has_ipv4) {
+ char ip_string[ 16 ];
+ sprintf(
+ ip_string,
+ "%d.%d.%d.%d",
+ neighbor.v4_addr[ 0 ],
+ neighbor.v4_addr[ 1 ],
+ neighbor.v4_addr[ 2 ],
+ neighbor.v4_addr[ 3 ]);
+
+ result = study(
+ ip_string, default_port, send_data, send_len, &response_data, &response_len);
+ } else if (neighbor.has_ipv6) {
+ char ip_string[ 46 ];
+ int offset = 0;
+ for (int j = 0; j < 8; j++) {
+ uint16_t word
+ = (neighbor.v6_addr[ j * 2 ] << 8) | neighbor.v6_addr[ j * 2 + 1 ];
+ offset += sprintf(ip_string + offset, "%x%s", word, (j < 7) ? ":" : "");
+ }
+
+ result = study(
+ ip_string, default_port, send_data, send_len, &response_data, &response_len);
+ }
+
+ if (result != 0) { fprintf(stderr, "failed to send packet to neighbor\n"); }
+
+ if (response_data) { free(response_data); }
+ }
+ }
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+
+ free(send_data);
+ free(_packet);
+ return NULL;
+}
+
+void save_packet(const struct data_packet packet);
+
+#define reply(__len, __stream) \
+ ({ \
+ *out = (byte*) __stream; \
+ *out_len = __len; \
+ })
+
+void paste_server(
+ char* ip_addr, size_t ip_size, byte* in, size_t in_len, byte** out, size_t* out_len) {
+ log(_info, "<%.*s> incoming request with size %li\n", (int) ip_size, ip_addr, in_len);
+
+ if (in_len < 8) { // server mandates 8 bytes minimum. pad it if you don't need it!
+ reply(17, strdup("\x01input too small"));
+ return;
+ }
+
+ enum client_input input_type = in[ 0 ];
+
+ switch (input_type) {
+ case audit_server: {
+ log(_info, "<%.*s> request type: audit\n", (int) ip_size, ip_addr);
+
+ byte* output_data = calloc(1 + 237, sizeof(byte));
+ if (!output_data) {
+ byte* err = calloc(1 + 14, sizeof(byte));
+ if (err) {
+ err[ 0 ] = error;
+ memcpy(err + 1, "server OOM", 10);
+ reply(11, err);
+ }
+ return;
+ }
+ output_data[ 0 ] = audit_response;
+
+ struct relay_info info = { 0 };
+
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ pthread_mutex_lock(&packet_storage_mutex);
+
+ info.major_version = server_stats.major_version;
+ info.minor_version = server_stats.minor_version;
+ info.capabilities.retention_hours = server_stats.retention_hours;
+ info.capabilities.can_store = packet_count < max_storage - 1;
+ info.capabilities.leading_zeros = server_stats.leading_zeros;
+ info.capabilities.accepts_plain = server_stats.accepts_plain;
+
+ info.statistics.neighbors = neighbor_count;
+ info.statistics.held_packets = packet_count;
+
+ // TODO: for now we return the first 4
+ for (int i = 0; i < 4; ++i) {
+ info.statistics.quick_connect[ i ] = neighbor_storage[ i ];
+ }
+
+ pthread_mutex_unlock(&packet_storage_mutex);
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+
+ memcpy(info.motd, server_stats.motd, 128);
+
+ relay_info2s(info, output_data + 1);
+
+ reply(1 + 237, output_data);
+ return;
+ }
+
+ case get_neighbors: {
+ byte count = in[ 1 ];
+ log(_info,
+ "<%.*s> request type: get neighbors, count %i\n",
+ (int) ip_size,
+ ip_addr,
+ count);
+
+ size_t output_len = 1 + (RELAY_NEIGHBOR_WIRE_LEN * count);
+ byte* output_data = calloc(output_len, sizeof(byte));
+ if (!output_data) {
+ byte* err = calloc(1 + 14, sizeof(byte));
+ if (err) {
+ err[ 0 ] = error;
+ memcpy(err + 1, "server OOM", 10);
+ reply(11, err);
+ }
+ return;
+ }
+ output_data[ 0 ] = neighbors_info;
+
+ size_t offset = 1;
+
+ for (byte i = 0; i < count; i++) {
+ // TODO: maybe mutex?
+ relay_neighbor2s(neighbor_storage[ i ], output_data + offset);
+ offset += RELAY_NEIGHBOR_WIRE_LEN;
+ }
+
+ reply(output_len, output_data);
+ return;
+ }
+
+ case get_packet: {
+ if (in_len < 1 + 32) {
+ byte* output_data = calloc(1 + 17, sizeof(byte));
+ if (!output_data) return;
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "input too small", 17);
+ reply(18, output_data);
+ return;
+ }
+
+ hash packet_hash;
+ bool success;
+ s2hash(32, in + 1, &success, packet_hash);
+
+ log(_info,
+ "<%.*s> request type: get packet with hash " hash_fmt "\n",
+ (int) ip_size,
+ ip_addr,
+ hash_ref(packet_hash));
+
+ if (!success) {
+ byte* output_data = calloc(1 + 12, sizeof(byte));
+ if (!output_data) return;
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "invalid hash", 12);
+ reply(13, output_data);
+ return;
+ }
+
+ struct data_packet_entry* entry = get_data_packet(packet_hash);
+ if (entry == NULL) {
+ byte* output_data = calloc(1 + 14, sizeof(byte));
+ if (!output_data) return;
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "packet not found", 14);
+ reply(15, output_data);
+ return;
+ }
+
+ size_t output_len = 1 + PACKET_SIZE;
+ byte* output_data = calloc(output_len, sizeof(byte));
+ if (!output_data) {
+ byte* err = calloc(1 + 14, sizeof(byte));
+ if (err) {
+ err[ 0 ] = error;
+ memcpy(err + 1, "server OOM", 10);
+ reply(11, err);
+ }
+ return;
+ }
+
+ output_data[ 0 ] = get_packet_result;
+ data_packet2s(entry->packet, output_data + 1);
+
+ reply(output_len, output_data);
+ return;
+ }
+
+ case push_packet: {
+ bool success;
+ struct data_packet packet = s2data_packet(in_len - 1, in + 1, &success);
+
+ log(_info,
+ "<%.*s> request type: push packet with hash " hash_fmt "\n",
+ (int) ip_size,
+ ip_addr,
+ hash_ref(packet.hash));
+
+ size_t output_len = 1 + 1;
+ byte* output_data = calloc(output_len, sizeof(byte));
+ if (!output_data) { return; }
+ output_data[ 0 ] = push_packet_result;
+
+ if (!success) {
+ log(_warn, "<%.*s> malformed data\n", (int) ip_size, ip_addr);
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ if (!check_leading_zeros(packet.hash, server_stats.leading_zeros)) {
+ log(_warn, "bad leads\n");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ const size_t hash_input_len = 4 + CONTENTS_SIZE;
+ byte hash_input_buffer[ hash_input_len ];
+ write_u32_le(hash_input_buffer, packet.nonce);
+ 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);
+
+ struct sha256 calculated_hash_struct = sha256_hash(hash_input_buffer, hash_input_len);
+ if (memcmp(calculated_hash_struct.hash, packet.hash, sizeof(hash)) != 0) {
+ log(_warn,
+ "bad hash " hash_fmt " vs " hash_fmt "\n",
+ hash_ref(calculated_hash_struct.hash),
+ hash_ref(packet.hash));
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ uint64_t current_time = get_current_time_ms();
+ if (packet.contents.created_time_ms > current_time) {
+ log(_warn, "future\n");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ if (!packet.contents.encrypted && !server_stats.accepts_plain) {
+ log(_warn, "plaintext not accepted by server\n");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ bool sig_ok = false;
+ if (!packet.contents.encrypted) {
+ size_t msg_len = CONTENTS_SIZE - sizeof(signature);
+ sig_ok = verify_raw_signature(
+ contents_buffer,
+ msg_len,
+ packet.contents.signature,
+ packet.contents.author_signing_pub);
+ } else {
+ secure_payload sp;
+ memcpy(sp.nonce, packet.contents.data, sizeof(sp.nonce));
+ memcpy(
+ sp.encrypted_data,
+ packet.contents.data + sizeof(sp.nonce),
+ sizeof(sp.encrypted_data));
+ memcpy(sp.signature, packet.contents.signature, sizeof(sp.signature));
+ sig_ok = verify_signature(sp, packet.contents.author_signing_pub);
+ }
+
+ if (!sig_ok) {
+ log(_warn, "bad signature\n");
+ output_data[ 1 ] = false;
+ reply(output_len, output_data);
+ return;
+ }
+
+ uint64_t storage_index = set_data_packet(&packet);
+ firehose_push(packet.hash, storage_index);
+
+ if (storage_index != minus_one) {
+ output_data[ 1 ] = true;
+
+ save_packet(packet);
+
+ struct data_packet* thread_arg = malloc(sizeof(struct data_packet));
+ if (thread_arg) {
+ *thread_arg = packet;
+ pthread_t prop_thread;
+ pthread_create(&prop_thread, NULL, propagate_packet, thread_arg);
+ pthread_detach(prop_thread);
+ }
+ } else {
+ output_data[ 1 ] = false;
+ }
+
+ reply(output_len, output_data);
+ return;
+ }
+
+ case firehose: {
+ if (in_len < 1 + 9) {
+ byte* output_data = calloc(1 + 17, sizeof(byte));
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "input too small", 17);
+ reply(18, output_data);
+ return;
+ }
+ struct firehose_request req;
+ bool success;
+ req = s2firehose_request(in_len - 1, in + 1, &success);
+
+ if (unlikely(req.count > 64)) req.count = 64; // clamp
+
+ log(_info,
+ "<%.*s> request type: firehose count %i offset %li\n",
+ (int) ip_size,
+ ip_addr,
+ req.count,
+ req.offset);
+
+ if (!success) {
+ byte* output_data = calloc(1 + 14, sizeof(byte));
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "invalid request", 14);
+ reply(15, output_data);
+ return;
+ }
+
+ water_splash entries[ req.count ];
+ uint16_t copied_count = firehose_get_packets(req.offset, req.count, entries);
+
+ size_t total_packets_size = PACKET_SIZE * copied_count;
+
+ size_t output_len = 1 + 8 + total_packets_size;
+ byte* output_data = calloc(output_len, sizeof(byte));
+ output_data[ 0 ] = firehose_reply;
+ size_t offset = 1;
+
+ uint64_t new_offset = req.offset + copied_count;
+ output_data[ offset++ ] = new_offset & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 8) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 16) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 24) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 32) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 40) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 48) & 0xFF;
+ output_data[ offset++ ] = (new_offset >> 56) & 0xFF;
+
+ for (int i = 0; i < copied_count; i++) {
+ struct data_packet_entry* entry = &packet_storage[ entries[ i ].storage_index ];
+ data_packet2s(entry->packet, output_data + offset);
+ offset += PACKET_SIZE;
+ }
+
+ reply(output_len, output_data);
+ return;
+ }
+
+ default: {
+ log(_info, "<%.*s> request type: invalid method\n", (int) ip_size, ip_addr);
+
+ byte* output_data = calloc(1 + 14, sizeof(byte));
+ output_data[ 0 ] = error;
+ memcpy(output_data + 1, "invalid input", 14);
+ reply(15, output_data);
+ return;
+ }
+ }
+}
+
+bool try_hugepages = true;
+
+void* alloc_map(uint64_t size) {
+ static const byte RW = PROT_READ | PROT_WRITE;
+ static const byte PA = MAP_PRIVATE | MAP_ANONYMOUS;
+
+ void* alloc;
+ if (try_hugepages) {
+ alloc = mmap(NULL, size, RW, PA | MAP_HUGE_2MB, -1, 0);
+ if (alloc == MAP_FAILED) {
+ // alloc failed. maybe the fault of hugepages?
+ goto NO_HP;
+ }
+ } else {
+ NO_HP:
+ alloc = mmap(NULL, size, RW, PA, -1, 0);
+ if (alloc != MAP_FAILED) {
+ // hugepages failed, remove flag
+ printf("[note] no hugepages support present\n");
+ try_hugepages = false;
+ }
+ }
+
+ if (alloc == MAP_FAILED) return NULL;
+ return alloc;
+}
+
+pthread_t packet_cleanup_thread;
+volatile bool packet_cleanup_thread_running = true;
+
+pthread_t disk_persistence_thread;
+volatile bool disk_persistence_thread_running = true;
+
+bool file_exists(const char* filename) { return access(filename, F_OK) == 0; }
+
+void save_packet(const struct data_packet packet) {
+ log(_debug, "saving packet " hash_fmt " to disk\n", hash_ref(packet.hash));
+
+ mkdir("packets", 0755);
+
+ char filename[ 128 ];
+ sprintf(filename, "packets/" hash_fmt ".bin", hash_ref(packet.hash));
+
+ FILE* file = fopen(filename, "wb");
+ if (!file) {
+ perror("failed to save packet");
+ return;
+ }
+
+ byte* buffer = malloc(PACKET_SIZE);
+ if (!buffer) {
+ fclose(file);
+ return;
+ }
+
+ data_packet2s(packet, buffer);
+ size_t written = fwrite(buffer, 1, PACKET_SIZE, file);
+ if (written != PACKET_SIZE) { perror("failed to write full packet to disk"); }
+ free(buffer);
+ fclose(file);
+}
+
+void save_neighbors() {
+ FILE* file = fopen("neighbors.bin", "wb");
+ if (!file) {
+ perror("failed to save neighbors");
+ return;
+ }
+
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ for (uint64_t i = 0; i < max_neighbors; i++) {
+ if (!empty_neighbor_slots[ i ]) {
+ byte buffer[ RELAY_NEIGHBOR_WIRE_LEN ];
+ relay_neighbor2s(neighbor_storage[ i ], buffer);
+ if (fwrite(buffer, 1, RELAY_NEIGHBOR_WIRE_LEN, file) != RELAY_NEIGHBOR_WIRE_LEN) {
+ perror("failed to write neighbor entry");
+ }
+ }
+ }
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+ fclose(file);
+}
+
+void load_neighbors() {
+ FILE* file = fopen("neighbors.bin", "rb");
+ if (!file) { return; }
+
+ size_t count = 0;
+
+ byte buffer[ RELAY_NEIGHBOR_WIRE_LEN ];
+ while (fread(buffer, 1, RELAY_NEIGHBOR_WIRE_LEN, file) == RELAY_NEIGHBOR_WIRE_LEN) {
+ bool success;
+ struct relay_neighbor neighbor
+ = s2relay_neighbor(RELAY_NEIGHBOR_WIRE_LEN, buffer, &success);
+ if (success) {
+ count++;
+ add_neighbor(neighbor);
+ }
+ }
+ fclose(file);
+
+ log(_info, "loaded %li neighbors\n", count);
+}
+
+void* fs_persistence(void* arg) {
+ while (disk_persistence_thread_running) {
+ sleep(2 * 60); // 2m
+ log(_info, "saving neighbors...\n");
+ save_neighbors();
+ }
+
+ return NULL;
+}
+
+void* cleanup_packets(void* arg) {
+ while (packet_cleanup_thread_running) {
+ sleep(30 * 60); // 30m
+
+ log(_warn, "running cleanup\n");
+ uint64_t current_time_ms = get_current_time_ms();
+ uint64_t retention_ms = (uint64_t) server_stats.retention_hours * 60 * 60 * 1000;
+
+ for (uint64_t i = 0; i < max_storage; i++) {
+ pthread_mutex_lock(&packet_storage_mutex);
+ if (packet_storage[ i ].is_full
+ && current_time_ms - packet_storage[ i ].packet.contents.created_time_ms
+ > retention_ms) {
+ log(_debug,
+ "cleaning up old packet hashed " hash_fmt "\n",
+ hash_ref(packet_storage[ i ].packet.hash));
+
+ char filename[ 128 ];
+ sprintf(
+ filename,
+ "packets/" hash_fmt ".bin",
+ hash_ref(packet_storage[ i ].packet.hash));
+
+ if (remove(filename) != 0) log(_error, "failed to delete packet %s\n", filename);
+
+ packet_storage[ i ].is_full = false;
+ packet_count--;
+ }
+ pthread_mutex_unlock(&packet_storage_mutex);
+ }
+ }
+
+ return NULL;
+}
+
+pthread_t neighbor_check_thread;
+volatile bool neighbor_check_thread_running = true;
+
+void* check_neighbors(void* arg) {
+ sleep(5 * 30);
+
+ while (neighbor_check_thread_running) {
+ pthread_mutex_lock(&neighbor_storage_mutex);
+ for (uint64_t i = 0; i < max_neighbors; i++) {
+ if (!empty_neighbor_slots[ i ]) {
+ struct relay_neighbor neighbor = neighbor_storage[ i ];
+
+ byte* response_data = NULL;
+ size_t response_len = 0;
+ int result = -1;
+
+ byte audit_request[ 1 ] = { audit_server };
+
+ if (neighbor.has_ipv4) {
+ char ip_string[ 16 ];
+ sprintf(
+ ip_string,
+ "%d.%d.%d.%d",
+ neighbor.v4_addr[ 0 ],
+ neighbor.v4_addr[ 1 ],
+ neighbor.v4_addr[ 2 ],
+ neighbor.v4_addr[ 3 ]);
+ result = study(
+ ip_string, default_port, audit_request, 1, &response_data, &response_len);
+ } else if (neighbor.has_ipv6) {
+ char ip_string[ 46 ];
+ int offset = 0;
+
+ for (int j = 0; j < 8; j++) {
+ uint16_t word
+ = (neighbor.v6_addr[ j * 2 ] << 8) | neighbor.v6_addr[ j * 2 + 1 ];
+
+ offset += snprintf(
+ ip_string + offset,
+ sizeof(ip_string) - offset,
+ "%x%s",
+ word,
+ (j < 7) ? ":" : "");
+ }
+
+ result = study(
+ ip_string, default_port, audit_request, 1, &response_data, &response_len);
+ }
+
+ if (result != 0 || response_data[ 0 ] != audit_response) {
+ empty_neighbor_slots[ i ] = true;
+ neighbor_count--;
+ log(_warn, "removed dead neighbor\n");
+ }
+
+ if (response_data) { free(response_data); }
+ }
+ }
+ pthread_mutex_unlock(&neighbor_storage_mutex);
+
+ sleep(60 * 5); // 5m
+ }
+ return NULL;
+}
+
+int main() {
+ fprintf(
+ stderr,
+ "-- moonpaste server v%i.%i --\n"
+ "port %i\n\n",
+ server_stats.major_version,
+ server_stats.minor_version,
+ default_port);
+
+ // init
+ packet_storage = alloc_map(max_storage * sizeof(struct data_packet_entry));
+
+ neighbor_storage = alloc_map(max_neighbors * sizeof(struct relay_neighbor));
+ empty_neighbor_slots = alloc_map(max_neighbors * sizeof(bool));
+ memset(empty_neighbor_slots, 1, max_neighbors * sizeof(bool));
+
+ if (!packet_storage || !neighbor_storage || !empty_neighbor_slots) {
+ perror("init alloc failed");
+ return 1;
+ }
+
+ if (pthread_create(&packet_cleanup_thread, NULL, cleanup_packets, NULL) != 0) {
+ perror("failed to create packet cleanup thread");
+ return 1;
+ }
+
+ load_neighbors();
+
+ if (pthread_create(&disk_persistence_thread, NULL, fs_persistence, NULL) != 0) {
+ perror("failed to create persistence thread");
+ return 1;
+ }
+
+ if (pthread_create(&neighbor_check_thread, NULL, check_neighbors, NULL) != 0) {
+ perror("failed to create neighbor audit thread");
+ return 1;
+ }
+
+ firehose_init();
+
+ omen(default_port, paste_server);
+
+ packet_cleanup_thread_running = false;
+ pthread_join(packet_cleanup_thread, NULL);
+
+ return 0;
+}
diff --git a/sha256.c b/sha256.c
new file mode 100644
index 0000000..0d19110
--- /dev/null
+++ b/sha256.c
@@ -0,0 +1,104 @@
+#include <stdint.h>
+#include <string.h>
+
+typedef uint8_t byte;
+
+typedef byte hash[ 32 ];
+
+static uint32_t ror(uint32_t x, int n) { return (x >> n) | (x << (32 - n)); }
+
+static uint32_t ch(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); }
+
+static uint32_t maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); }
+
+static uint32_t sigma0(uint32_t x) { return ror(x, 2) ^ ror(x, 13) ^ ror(x, 22); }
+
+static uint32_t sigma1(uint32_t x) { return ror(x, 6) ^ ror(x, 11) ^ ror(x, 25); }
+
+static uint32_t gamma0(uint32_t x) { return ror(x, 7) ^ ror(x, 18) ^ (x >> 3); }
+
+static uint32_t gamma1(uint32_t x) { return ror(x, 17) ^ ror(x, 19) ^ (x >> 10); }
+
+static const uint32_t K[ 64 ] = {
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+static void compress(uint32_t state[ 8 ], const byte block[ 64 ]) {
+ uint32_t w[ 64 ];
+ int i;
+ for (i = 0; i < 16; i++) {
+ w[ i ] = (block[ i * 4 ] << 24) | (block[ i * 4 + 1 ] << 16) | (block[ i * 4 + 2 ] << 8)
+ | block[ i * 4 + 3 ];
+ }
+ for (i = 16; i < 64; i++) {
+ w[ i ] = gamma1(w[ i - 2 ]) + w[ i - 7 ] + gamma0(w[ i - 15 ]) + w[ i - 16 ];
+ }
+ uint32_t a = state[ 0 ], b = state[ 1 ], c = state[ 2 ], d = state[ 3 ], e = state[ 4 ],
+ f = state[ 5 ], g = state[ 6 ], h = state[ 7 ];
+ for (i = 0; i < 64; i++) {
+ uint32_t t1 = h + sigma1(e) + ch(e, f, g) + K[ i ] + w[ i ];
+ uint32_t t2 = sigma0(a) + maj(a, b, c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + t1;
+ d = c;
+ c = b;
+ b = a;
+ a = t1 + t2;
+ }
+ state[ 0 ] += a;
+ state[ 1 ] += b;
+ state[ 2 ] += c;
+ state[ 3 ] += d;
+ state[ 4 ] += e;
+ state[ 5 ] += f;
+ state[ 6 ] += g;
+ state[ 7 ] += h;
+}
+
+struct sha256 {
+ hash hash;
+} sha256_hash(const byte *in, size_t in_len) {
+ struct sha256 out;
+
+ uint32_t state[ 8 ] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
+ size_t off = 0;
+ for (; off + 64 <= in_len; off += 64) { compress(state, in + off); }
+ byte pad[ 128 ];
+ memset(pad, 0, sizeof(pad));
+ size_t rem = in_len - off;
+ memcpy(pad, in + off, rem);
+ pad[ rem ] = 0x80;
+ int use_two_blocks = (rem >= 56);
+ size_t pad_len = 64 + 64 * use_two_blocks;
+ size_t pos = rem + 1;
+ while (pos < pad_len - 8) { pad[ pos++ ] = 0; }
+ uint64_t bit_len = in_len * 8;
+ pad[ pad_len - 1 ] = bit_len & 0xff;
+ pad[ pad_len - 2 ] = (bit_len >> 8) & 0xff;
+ pad[ pad_len - 3 ] = (bit_len >> 16) & 0xff;
+ pad[ pad_len - 4 ] = (bit_len >> 24) & 0xff;
+ pad[ pad_len - 5 ] = (bit_len >> 32) & 0xff;
+ pad[ pad_len - 6 ] = (bit_len >> 40) & 0xff;
+ pad[ pad_len - 7 ] = (bit_len >> 48) & 0xff;
+ pad[ pad_len - 8 ] = (bit_len >> 56) & 0xff;
+ compress(state, pad);
+ if (use_two_blocks) { compress(state, pad + 64); }
+ for (int i = 0; i < 8; i++) {
+ out.hash[ 4 * i ] = (state[ i ] >> 24) & 0xff;
+ out.hash[ 4 * i + 1 ] = (state[ i ] >> 16) & 0xff;
+ out.hash[ 4 * i + 2 ] = (state[ i ] >> 8) & 0xff;
+ out.hash[ 4 * i + 3 ] = state[ i ] & 0xff;
+ }
+
+ return out;
+} \ No newline at end of file