summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesper Jensen <jesper@jnsn.dev>2025-07-18 09:32:16 +0200
committerJesper Jensen <jesper@jnsn.dev>2025-07-18 09:32:33 +0200
commit69528489cddd27b4d4b9411d00b60cccc175b0e8 (patch)
treeeeed9d13f99858b3fd5660bc8d57e86bf81088a3
parenta46e9a5cbb8fc233a0ccb47708e7686c2b561ac0 (diff)
Add a simple API for lookups
-rw-r--r--Makefile4
-rw-r--r--src/api.c311
-rw-r--r--src/api.h6
-rw-r--r--src/base64.c115
-rw-r--r--src/base64.h12
-rw-r--r--src/benc.c4
-rw-r--r--src/main.c77
-rw-r--r--src/peers.c2
-rw-r--r--src/proto.c20
-rw-r--r--src/proto.h5
-rw-r--r--test/api.c246
-rw-r--r--test/proto.c71
12 files changed, 775 insertions, 98 deletions
diff --git a/Makefile b/Makefile
index 713ea21..3e3d498 100644
--- a/Makefile
+++ b/Makefile
@@ -24,7 +24,7 @@ TEST_LIB_SOURCES = thirdparty/Unity/src/unity.c
TEST_LIB_OBJS = $(TEST_LIB_SOURCES:%.c=$(OBJDIR)/%.o)
TEST_LIB_DEPS = $(TEST_LIB_SOURCES:%.c=%.d)
TEST_LIB_INCS = -Ithirdparty/Unity/src/
-TEST_LIB_CFLAGS = -DUNITY_INCLUDE_DOUBLE
+TEST_LIB_CFLAGS = -DUNITY_INCLUDE_DOUBLE -lcurl
TEST_SOURCES = $(shell find $(TSTDIR) -name "*.c")
TEST_EXES = $(TEST_SOURCES:%.c=$(OBJDIR)/%)
@@ -62,7 +62,7 @@ test: $(TEST_EXES)
$(foreach test,$(TEST_EXES),./$(test) &&) true
$(OBJDIR)/test/%: $(APP_OBJS) $(TEST_LIB_OBJS) $(OBJDIR)/test/%.o $(OBJDIR)/test/%.runner.o
- $(CC) $(LDFLAGS) $(CFLAGS) -o $@ $^ $(LIBS)
+ $(CC) $(LDFLAGS) $(TEST_LIB_CFLAGS) $(CFLAGS) -o $@ $^ $(LIBS)
$(OBJDIR)/test/%.runner.c: test/%.c
@mkdir -p $(dir $@)
diff --git a/src/api.c b/src/api.c
new file mode 100644
index 0000000..ffcd60f
--- /dev/null
+++ b/src/api.c
@@ -0,0 +1,311 @@
+#include "api.h"
+#include "log.h"
+#include "base64.h"
+#include "metrics.h"
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <microhttpd.h>
+#include <string.h>
+
+#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
+
+const char *lookupStateStr[OP_LEN] = {
+ [OP_EMPTY] = "empty",
+ [OP_PENDING] = "pending",
+ [OP_ACTIVE] = "active",
+ [OP_COMPLETED] = "completed",
+};
+
+struct api {
+ struct dht *dht;
+};
+
+enum ReqType {
+ REQ_PUT_LOOKUP,
+};
+
+enum JsonState {
+ JSTATE_STR,
+};
+
+struct request {
+ enum ReqType type;
+
+ bool target_set;
+ struct nodeid target;
+ enum Operation state;
+};
+
+static enum MHD_Result queue_lookup_response(struct api *api, struct MHD_Connection *connection, struct MHD_Response **response, bool locked) {
+ enum MHD_Result ret;
+
+ // All states are named
+ BUILD_BUG_ON((sizeof(lookupStateStr) / sizeof(lookupStateStr[0])) != OP_LEN);
+
+ char buf[1024];
+ char *cursor = buf;
+ char *buf_end = buf + sizeof(buf);
+ size_t len;
+
+ if(!locked) pthread_mutex_lock(&api->dht->mutex);
+ len = snprintf(cursor, buf_end-cursor, "{ \"state\": \"%s\"", lookupStateStr[api->dht->lookup.state]);
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+
+ if(api->dht->lookup.state == OP_ACTIVE || api->dht->lookup.state == OP_COMPLETED || api->dht->lookup.state == OP_PENDING) {
+ len = snprintf(cursor, buf_end-cursor, ", \"outstanding\": %ld, \"target\": \"", api->dht->lookup.outstanding);
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+
+ len = base64_encode_inplace((void*)&api->dht->lookup.target, sizeof(struct nodeid), cursor, buf_end - cursor);
+ if(len < 0) fatal("base64 failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+
+ len = snprintf(cursor, buf_end-cursor, "\"");
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+ }
+
+ if(api->dht->lookup.state == OP_COMPLETED || api->dht->lookup.state == OP_ACTIVE) {
+ len = snprintf(cursor, buf_end-cursor, ", \"result\": [");
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+
+ char *lead = "";
+ for(size_t i = 0; i < 8; i++) {
+ len = snprintf(cursor, buf_end-cursor, "%s{\"id\": \"", lead);
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+
+ len = base64_encode_inplace((void*)&api->dht->lookup.closest[i], sizeof(struct nodeid), cursor, buf_end - cursor);
+ if(len < 0) fatal("base64 failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+
+ len = snprintf(cursor, buf_end-cursor, "\"}");
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+
+ lead = ", ";
+ }
+
+ len = snprintf(cursor, buf_end-cursor, "]");
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+ }
+
+
+ len = snprintf(cursor, buf_end-cursor, " }");
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+ pthread_mutex_unlock(&api->dht->mutex);
+
+ *response = MHD_create_response_from_buffer_copy(cursor - buf, (void *)buf);
+ ret = MHD_add_response_header(*response, "Content-Type", "application/json");
+ if(ret != MHD_YES) return ret;
+
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, *response);
+ return ret;
+}
+
+static enum MHD_Result handler(
+ void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls
+) {
+ struct api *api = (struct api*)cls;
+ struct request * conn = *con_cls;
+ enum MHD_Result ret = MHD_NO;
+ struct MHD_Response *response = NULL;
+
+ if(conn != NULL) {
+ if(*upload_data_size > 0) {
+ const char* cursor = upload_data;
+
+ if(memcmp(cursor, "{", 1) != 0) return MHD_NO;
+ cursor += 1;
+
+ while(true) {
+ if(memcmp(cursor, "\"target\": \"", 11) == 0) {
+ cursor += 11;
+
+ conn->target_set = true;
+ size_t read = base64_decode_incr((unsigned char*)cursor, *upload_data_size - (cursor - upload_data), (void*)&conn->target, sizeof(struct nodeid));
+ if(read < 0) return MHD_NO;
+ // @CLEANUP This isn't necessarily correct since we also accept
+ // base64's with invalid final padding. Hopefully that will just
+ // lead to a bad error message to the user, but we have to look at
+ // that more carefully when this code is done.
+ cursor += 4 * ((read + 2) / 3);
+
+ if(memcmp(cursor, "\"", 1) != 0) return MHD_NO;
+ cursor += 1;
+ } else if(memcmp(cursor, "\"state\": \"", 10) == 0) {
+ cursor += 10;
+
+ for(size_t i = 0; i < OP_LEN; i++) {
+ size_t lookupLen = strlen(lookupStateStr[i]);
+ if(memcmp(cursor, lookupStateStr[i], lookupLen) == 0) {
+ cursor += lookupLen;
+ conn->state = i;
+ break;
+ }
+ }
+
+ if(memcmp(cursor, "\"", 1) != 0) return MHD_NO;
+ cursor += 1;
+ } else {
+ return MHD_NO;
+ }
+
+ if(memcmp(cursor, ", ", 2) != 0) break;
+ cursor += 2;
+ }
+
+ if(memcmp(cursor, "}", 1) != 0) return MHD_NO;
+ cursor += 1;
+
+ *upload_data_size = (*upload_data_size - (cursor - upload_data));
+
+ return MHD_YES;
+ } else {
+
+ pthread_mutex_lock(&api->dht->mutex);
+
+ if(conn->state == OP_PENDING) {
+ if(!conn->target_set) {
+ pthread_mutex_unlock(&api->dht->mutex);
+ return MHD_NO;
+ }
+
+ if(api->dht->lookup.state != OP_EMPTY) {
+ pthread_mutex_unlock(&api->dht->mutex);
+ // @COMPL We should return some nice error message to the user
+ // here about how they lost a race
+ return MHD_NO;
+ }
+
+ // Start a lookup
+ memcpy(&api->dht->lookup.target, &conn->target, sizeof(struct nodeid));
+ api->dht->lookup.state = OP_PENDING;
+ prom_gauge_set(lookup_state, api->dht->lookup.state, NULL);
+ } else if(conn->state == OP_EMPTY) {
+ if(conn->target_set) {
+ pthread_mutex_unlock(&api->dht->mutex);
+ return MHD_NO;
+ }
+
+ if(api->dht->lookup.state != OP_COMPLETED) {
+ pthread_mutex_unlock(&api->dht->mutex);
+ // @COMPL We should return some nice error message to the user
+ // here about how they lost a race
+ return MHD_NO;
+ }
+
+ api->dht->lookup.state = OP_EMPTY;
+ prom_gauge_set(lookup_state, api->dht->lookup.state, NULL);
+ } else {
+ pthread_mutex_unlock(&api->dht->mutex);
+ return MHD_NO;
+ }
+
+ ret = queue_lookup_response(api, connection, &response, true);
+ goto end;
+ }
+ }
+
+ if(strcmp(url, "/") == 0) {
+ if(strcmp(method, "GET") != 0) {
+ response = MHD_create_response_from_buffer_static(0, NULL);
+ ret = MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, response);
+ goto end;
+ }
+
+ char buf[1024];
+ char *cursor = buf;
+ char *buf_end = buf + sizeof(buf);
+ size_t len;
+
+ pthread_mutex_lock(&api->dht->mutex);
+ len = snprintf(cursor, buf_end-cursor, "{ \"id\": \"");
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer too short");
+
+ len = base64_encode_inplace((void*)&api->dht->self, sizeof(struct nodeid), cursor, buf_end - cursor);
+ if(len < 0) fatal("base64 failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer to short");
+
+ len = snprintf(cursor, buf_end-cursor, "\" }");
+ if(len < 0) fatal("printf failed\n");
+ cursor += len;
+ if(cursor >= buf_end) fatal("buffer too short");
+ pthread_mutex_unlock(&api->dht->mutex);
+
+ response = MHD_create_response_from_buffer_copy(cursor - buf, (void *)buf);
+ ret = MHD_add_response_header(response, "Content-Type", "application/json");
+ if(ret != MHD_YES) goto end;
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ goto end;
+ }
+
+ if(strcmp(url, "/lookup") == 0) {
+ if(strcmp(method, "GET") == 0) {
+ ret = queue_lookup_response(api, connection, &response, false);
+ goto end;
+ } else if(strcmp(method, "PUT") == 0) {
+ conn = calloc(1, sizeof(struct request));
+
+ conn->type = REQ_PUT_LOOKUP;
+
+ *con_cls = conn;
+ return MHD_YES;
+ }
+
+ response = MHD_create_response_from_buffer_static(0, NULL);
+ ret = MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, response);
+ goto end;
+ }
+
+ response = MHD_create_response_from_buffer_static(0, NULL);
+ ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response);
+end:
+ assert(response == NULL || ret == MHD_YES);
+ if(response != NULL) MHD_destroy_response(response);
+ return ret;
+}
+
+#define PORT 6982
+static struct MHD_Daemon *mDaemon;
+static struct api api;
+
+void api_init(struct dht *dht) {
+ api.dht = dht;
+
+ mDaemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT, NULL, NULL, &handler, &api, MHD_OPTION_END);
+ if(mDaemon == NULL) {
+ fatal("Failed to start http server");
+ }
+ dbg("API server started on port %d", PORT);
+}
+
+void api_end() {
+ MHD_stop_daemon(mDaemon);
+ dbg("API server stopped");
+}
diff --git a/src/api.h b/src/api.h
new file mode 100644
index 0000000..953b7a2
--- /dev/null
+++ b/src/api.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "proto.h"
+
+void api_init(struct dht *dht);
+void api_end();
diff --git a/src/base64.c b/src/base64.c
new file mode 100644
index 0000000..b8625c3
--- /dev/null
+++ b/src/base64.c
@@ -0,0 +1,115 @@
+#include "base64.h"
+#include "log.h"
+
+#include <stdbool.h>
+
+static char encoding_table[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/'
+};
+static int mod_table[] = {0, 2, 1};
+ssize_t base64_encode_inplace(const uint8_t *data, size_t data_len, char* buf, size_t buf_len) {
+ size_t output_len = 4 * ((data_len + 2) / 3);
+
+ if(output_len > buf_len) return output_len;
+
+ for (int i = 0, j = 0; i < data_len;) {
+ uint32_t octet_a = i < data_len ? (unsigned char)data[i++] : 0;
+ uint32_t octet_b = i < data_len ? (unsigned char)data[i++] : 0;
+ uint32_t octet_c = i < data_len ? (unsigned char)data[i++] : 0;
+
+ uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+
+ buf[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
+ buf[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
+ buf[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
+ buf[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
+ }
+
+ for (int i = 0; i < mod_table[data_len % 3]; i++) {
+ buf[output_len - 1 - i] = '=';
+ }
+
+ buf[output_len] = '\0';
+
+ return output_len;
+}
+// @CLEANUP This really should exist, but I've kept it around as an adapter for
+// the code that uses it. Remove it at some point
+char *base64_encode(const unsigned char *data, size_t input_length, size_t *output_length) {
+ *output_length = base64_encode_inplace(data, input_length, NULL, 0);
+
+ char *encoded_data = malloc(*output_length + 1);
+ assert(encoded_data != NULL);
+
+ base64_encode_inplace(data, input_length, encoded_data, *output_length+1);
+ return encoded_data;
+}
+
+static int8_t decoding_table[] = {
+ 62, -1, -1, -1, 63, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, -1,
+ -1, -1, -1, -1, -1, -1, 0, 1,
+ 2, 3, 4, 5, 6, 7, 8, 9,
+ 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25,
+ -1, -1, -1, -1, -1, -1, 26, 27,
+ 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51
+};
+
+ssize_t base64_decode_incr(const unsigned char *data, size_t data_len, uint8_t *buf, size_t buf_len) {
+ size_t i = 0;
+ size_t j = 0;
+ uint8_t runoff = 0;
+ while(runoff == 0 && i < data_len) {
+ uint32_t triple = 0;
+ for(uint8_t k = 0; k < 4; k++) {
+ int8_t sextent = 0;
+ char cp = data[i];
+ if(cp < 43 || cp > 122) {
+ // NULL byte string terminator ends here
+ return k == 0 ? j : -1;
+ } else if(cp == '=' && k < 2) {
+ return -1;
+ } else if(cp == '=') {
+ sextent = 0;
+ runoff = 4-k;
+ break;
+ } else {
+ sextent = decoding_table[cp - 43];
+ if(sextent < 0) return k == 0 ? j : -1;
+ i++;
+ }
+
+ triple |= (sextent << (3-k) * 6);
+ }
+
+ if (j+runoff >= buf_len) return -1;
+ if(runoff <= 2) buf[j++] = (triple >> 2 * 8) & 0xFF;
+ if(runoff <= 1) buf[j++] = (triple >> 1 * 8) & 0xFF;
+ if(runoff <= 0) buf[j++] = (triple >> 0 * 8) & 0xFF;
+ }
+
+ return j;
+}
+
+ssize_t base64_decode(const unsigned char *data, size_t data_len, uint8_t *buf, size_t buf_len) {
+ if (data_len % 4 != 0) return -1;
+
+ size_t dest_len = data_len / 4 * 3;
+ if (data[data_len - 1] == '=') dest_len--;
+ if (data[data_len - 2] == '=') dest_len--;
+
+ if(buf_len < dest_len) return -1;
+
+ return base64_decode_incr(data, data_len, buf, buf_len);
+}
+
diff --git a/src/base64.h b/src/base64.h
new file mode 100644
index 0000000..6e52b87
--- /dev/null
+++ b/src/base64.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+ssize_t base64_encode_inplace(const uint8_t *data, size_t data_len, char* buf, size_t buf_len);
+char *base64_encode(const unsigned char *data, size_t input_length, size_t *output_length);
+
+ssize_t base64_decode_incr(const unsigned char *data, size_t data_len, uint8_t *buf, size_t buf_len);
+ssize_t base64_decode(const unsigned char *data, size_t data_len, uint8_t *buf, size_t buf_len);
diff --git a/src/benc.c b/src/benc.c
index c2c2782..9842dd9 100644
--- a/src/benc.c
+++ b/src/benc.c
@@ -56,7 +56,7 @@ void benc_print(const struct benc_node* stream, size_t stream_len) {
switch(cursor->type) {
case BNT_INT:
indent(depth);
- printf("INT %.*s\n", cursor->size, cursor->loc);
+ printf("INT %.*s\n", (int)cursor->size, cursor->loc);
break;
case BNT_STRING:
indent(depth);
@@ -69,7 +69,7 @@ void benc_print(const struct benc_node* stream, size_t stream_len) {
}
printf("STR ");
if(allprint) {
- printf("%.*s", cursor->size, cursor->loc);
+ printf("%.*s", (int)cursor->size, cursor->loc);
} else {
for (const unsigned char* c = (const unsigned char*)cursor->loc; c < (unsigned char*)(cursor->loc + cursor->size); c++) {
printf("\\x%02X", *c);
diff --git a/src/main.c b/src/main.c
index 02f39b1..bb1a67f 100644
--- a/src/main.c
+++ b/src/main.c
@@ -2,6 +2,8 @@
#include "peers.h"
#include "log.h"
#include "metrics.h"
+#include "base64.h"
+#include "api.h"
#include <time.h>
#include <sys/time.h>
@@ -12,47 +14,6 @@
#include <stdint.h>
#include <stdlib.h>
-static char encoding_table[] = {
- 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
- 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
- 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
- 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
- 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
- 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
- 'w', 'x', 'y', 'z', '0', '1', '2', '3',
- '4', '5', '6', '7', '8', '9', '+', '/'
-};
-static int mod_table[] = {0, 2, 1};
-
-
-char *base64_encode(const unsigned char *data, size_t input_length, size_t *output_length) {
- *output_length = 4 * ((input_length + 2) / 3);
-
- char *encoded_data = malloc(*output_length + 1);
- assert(encoded_data != NULL);
-
- for (int i = 0, j = 0; i < input_length;) {
- uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
- uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
- uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;
-
- uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
-
- encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
- encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
- encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
- encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
- }
-
- for (int i = 0; i < mod_table[input_length % 3]; i++) {
- encoded_data[*output_length - 1 - i] = '=';
- }
-
- encoded_data[*output_length] = 0;
-
- return encoded_data;
-}
-
static volatile bool killed = false;
void sigint_handler(int sig) {
killed = true;
@@ -122,6 +83,7 @@ void flush_messages(int sfd, struct message* cursor, const struct message* const
//now reply the client with the same data
int rc = sendto(sfd, cursor->payload, cursor->payload_len, 0, (const struct sockaddr*)&cursor->dest, cursor->dest_len);
if (rc < 0) {
+ dbg("dest_len %d", cursor->dest_len);
fatal("Failed to send message %d %m: %d", errno, cursor->dest_len);
}
prom_counter_add(bytesSent, cursor->payload_len, NULL);
@@ -145,6 +107,11 @@ int main(int argc, char** argv) {
fatal("Couldn't set signal handler");
struct dht dht = {0};
+ pthread_mutexattr_t mutexattr;
+ pthread_mutexattr_init(&mutexattr);
+ pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_ERRORCHECK);
+ pthread_mutex_init(&dht.mutex, &mutexattr);
+ pthread_mutex_lock(&dht.mutex);
{
routing_init(NULL);
int rc = read_config();
@@ -152,7 +119,6 @@ int main(int argc, char** argv) {
for(uint16_t i = 0; i < sizeof(myID.inner_b); i++) {
myID.inner_b[i] = rand();
}
- /* myID = (struct nodeid){.inner={0xebe9bbf1, 0x3cdba6b3, 0x993e0c87, 0x900d5e25, 0x00000000}}; */
routing_init(&myID);
allocate_hashtable();
} else {
@@ -164,6 +130,7 @@ int main(int argc, char** argv) {
}
metric_init();
+ api_init(&dht);
routing_update_metrics();
peer_update_metrics();
@@ -176,12 +143,6 @@ int main(int argc, char** argv) {
proto_begin(&dht, time(NULL), &message_cursor, outbuff+32);
flush_messages(dht.sfd, outbuff, message_cursor);
- time_t lookup_refresh = 0;
- // Init the lookup
- dht.lookup.target = (struct nodeid){.inner={0x19b8a941, 0x38fa0191, 0x1403fac2, 0x581000ab, 0x19583cda}};
- dht.lookup.state = OP_PENDING; // We want this to run at some point.
- prom_gauge_set(lookup_state, dht.lookup.state, NULL);
-
#define RECV_BUFF_SIZE 4096
char buff_storage[RECV_BUFF_SIZE+1];
int rc = 0;
@@ -190,10 +151,6 @@ int main(int argc, char** argv) {
bool timedout = false;
time_t next = dht.wake;
- if(lookup_refresh != 0 && difftime(lookup_refresh, next) < 0.0) {
- next = lookup_refresh;
- }
-
if(next != 0) {
time_t sleepfor = next - time(NULL);
struct timeval tv = {
@@ -212,7 +169,9 @@ int main(int argc, char** argv) {
socklen_t remote_len = sizeof(remote);
ssize_t recv_len;
if(!timedout) {
+ pthread_mutex_unlock(&dht.mutex);
recv_len = recvfrom(dht.sfd, buff, RECV_BUFF_SIZE, 0, (struct sockaddr *)&remote, &remote_len);
+ pthread_mutex_lock(&dht.mutex);
if(recv_len == -1) {
// This is really strange. The man pages say we should be getting
// an ETIMEDOUT here, but instead linux gives us this.
@@ -243,19 +202,6 @@ int main(int argc, char** argv) {
rc = proto_run(&dht, buff, recv_len, (struct sockaddr_in*)&remote, remote_len, now, &message_cursor, outbuff+OUTBOX_SIZE);
flush_messages(dht.sfd, outbuff, message_cursor);
- if(dht.lookup.state == OP_COMPLETED) {
- if(lookup_refresh == 0) {
- dbg("Lookup completed");
- lookup_refresh = now + 3600;
- } else if (difftime(lookup_refresh, now) < 0.0) {
- dbg("Restart LOOKUP");
- lookup_refresh = 0;
- // Restart the lookup periodically
- dht.lookup.state = OP_PENDING;
- prom_gauge_set(lookup_state, dht.lookup.state, NULL);
- }
- }
-
if(dht.dirtyconf) {
save_config();
dht.dirtyconf = false;
@@ -263,6 +209,7 @@ int main(int argc, char** argv) {
}
proto_end(&dht);
+ api_end();
metric_end();
save_config();
diff --git a/src/peers.c b/src/peers.c
index 3236a9a..7d393b0 100644
--- a/src/peers.c
+++ b/src/peers.c
@@ -219,7 +219,7 @@ void peer_update_metrics() {
for(size_t i = 0; i < peer_table_size; i++) {
if(!peer_table[i].set) continue;
computed_load++;
- if(now < peer_table[i].last_seen) dbg("%d was seen after now? (%ld < %ld)", i, now, peer_table[i].last_seen);
+ if(now < peer_table[i].last_seen) dbg("%ld was seen after now? (%ld < %ld)", i, now, peer_table[i].last_seen);
time_t entry_expire = peer_table[i].last_seen + HASH_TIMEOUT;
next_expire = (next_expire == -1 || entry_expire < next_expire) ? entry_expire : next_expire;
diff --git a/src/proto.c b/src/proto.c
index bd08c23..eb68566 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -398,7 +398,15 @@ PROCESS_REPONSE(lookup_response) {
for(size_t i = 0; i < 8; i++) {
if(cont->lookup->closest_addr[i].port == 0) {
match_i = i;
- match_score = UINT8_MAX; // Bogus value to signal that we found something
+ match_score = UINT8_MAX; // Bogus value to signal that we found an empty slot
+ break;
+ }
+
+ if(memcmp(&cont->lookup->closest[i], &id, sizeof(struct nodeid)) == 0) {
+ // If the nodeid is already present in the lookup, we just ignore
+ // it completely
+ match_score = 0;
+ match_i = i;
break;
}
@@ -419,16 +427,6 @@ PROCESS_REPONSE(lookup_response) {
dbg("Discarding response from node behind the frontier");
}
- uint8_t worst_match = UINT8_MAX;
- for(size_t i = 0; i < 8; i++) {
- if(cont->lookup->closest_addr[i].port == 0) {
- worst_match = 0;
- break;
- }
-
- worst_match = MIN(worst_match, prefix(&cont->lookup->closest[i], &cont->lookup->target));
- }
-
// Fan out the search if the nodes are better than the worst one in the frontier
for(uint8_t i = 0; i < nodes_len; i++) {
uint8_t candidate_score = prefix(&nodes[i], &cont->lookup->target);
diff --git a/src/proto.h b/src/proto.h
index 95c7d7e..aaa3b3b 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -4,6 +4,7 @@
#include "sha256.h"
#include <sys/socket.h>
#include <arpa/inet.h>
+#include <pthread.h>
#define TOKEN_ITMO 20
#define TOKEN_VTMO 60
@@ -26,6 +27,8 @@ enum Operation {
OP_PENDING,
OP_ACTIVE,
OP_COMPLETED,
+
+ OP_LEN,
};
struct lookup {
@@ -74,6 +77,8 @@ typedef PROCESS_REPONSE(resp);
typedef PROCESS_TIMEOUT(tmout);
struct dht {
+ pthread_mutex_t mutex;
+
struct nodeid self;
int sfd;
diff --git a/test/api.c b/test/api.c
new file mode 100644
index 0000000..c88ed46
--- /dev/null
+++ b/test/api.c
@@ -0,0 +1,246 @@
+#include "log.h"
+#include "unity.h"
+
+#include "api.h"
+#include "base64.h"
+
+#include <assert.h>
+#include <curl/curl.h>
+#include <stdlib.h>
+#include <string.h>
+
+// @PASTE Stolen from libcurl documentation
+struct memory {
+ char *body;
+ size_t size;
+};
+
+static size_t write_to_memory(char *data, size_t size, size_t nmemb, void *clientp) {
+ size_t realsize = size * nmemb;
+ struct memory *mem = (struct memory *)clientp;
+
+ char *ptr = realloc(mem->body, mem->size + realsize + 1);
+ if(!ptr) return 0; /* out of memory */
+
+ mem->body = ptr;
+ memcpy(&(mem->body[mem->size]), data, realsize);
+ mem->size += realsize;
+ mem->body[mem->size] = 0;
+
+ return realsize;
+}
+
+static size_t read_from_memory(char *data, size_t size, size_t nmemb, void *clientp) {
+ size_t realsize = size * nmemb;
+ struct memory *mem = (struct memory *)clientp;
+
+ realsize = realsize > mem->size ? mem->size : realsize;
+
+ memcpy(data, mem->body, realsize);
+ mem->size -= realsize;
+ mem->body += realsize;
+
+ return realsize;
+}
+
+void test_base64_decode() {
+ size_t res = 0;
+ uint8_t buf[256];
+
+ res = base64_decode((unsigned char*)"AAAA", 4, buf, 256);
+ TEST_ASSERT_EQUAL(3, res);
+ TEST_ASSERT_EQUAL_MEMORY("\0\0\0", buf, 3);
+
+ res = base64_decode((unsigned char*)"AA==", 4, buf, 256);
+ TEST_ASSERT_EQUAL(1, res);
+ TEST_ASSERT_EQUAL_MEMORY("\0", buf, 1);
+
+ res = base64_decode((unsigned char*)"MQ==", 4, buf, 256);
+ TEST_ASSERT_EQUAL(1, res);
+ TEST_ASSERT_EQUAL_MEMORY("1", buf, 1);
+
+ res = base64_decode((unsigned char*)"AAA=", 4, buf, 256);
+ TEST_ASSERT_EQUAL(2, res);
+ TEST_ASSERT_EQUAL_MEMORY("\0\0", buf, 2);
+
+ // Too short of an input string
+ res = base64_decode((unsigned char*)"AA=", 3, buf, 256);
+ TEST_ASSERT_EQUAL(-1, res);
+
+ // Too little space in the output buffer
+ res = base64_decode((unsigned char*)"AAA=", 4, buf, 1);
+ TEST_ASSERT_EQUAL(-1, res);
+
+ // Too little space in the output buffer
+ res = base64_decode((unsigned char*)"dGVzdA==", 8, buf, 256);
+ TEST_ASSERT_EQUAL(4, res);
+ TEST_ASSERT_EQUAL_MEMORY("test", buf, 4);
+
+ // A null byte in the middle of the input
+ res = base64_decode((unsigned char*)"AA\0=", 4, buf, 256);
+ TEST_ASSERT_EQUAL(-1, res);
+
+ // It should stop at the first equals
+ res = base64_decode_incr((unsigned char*)"AA==W", 5, buf, 256);
+ TEST_ASSERT_EQUAL(1, res);
+ TEST_ASSERT_EQUAL_MEMORY("\0", buf, 1);
+
+ res = base64_decode_incr((unsigned char*)"AAAA\"", 5, buf, 256);
+ TEST_ASSERT_EQUAL(3, res);
+ TEST_ASSERT_EQUAL_MEMORY("\0\0\0", buf, 3);
+
+ res = base64_decode_incr((unsigned char*)"AAAA}", 5, buf, 256);
+ TEST_ASSERT_EQUAL(3, res);
+ TEST_ASSERT_EQUAL_MEMORY("\0\0\0", buf, 3);
+
+ // We are a little overpermissive when it comes to the padding equals. This
+ // isn't technically valid, but due to some implementation details we still
+ // accept it. I think that's fine
+ res = base64_decode_incr((unsigned char*)"AA=W", 4, buf, 256);
+ TEST_ASSERT_EQUAL(1, res);
+ TEST_ASSERT_EQUAL_MEMORY("\0", buf, 1);
+}
+
+void test_root_get() {
+ CURLcode curlRes;
+ CURL *curl = curl_easy_init();
+ TEST_ASSERT_NOT_NULL(curl);
+
+ struct dht dht = {0};
+ dht.self = (struct nodeid){.inner={0x42424242, 0x42424242, 0x42424242, 0x42424242, 0x42424242}};
+ api_init(&dht);
+
+ curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:6982/");
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ struct memory body = {0};
+ curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_perform(curl);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ char *ct;
+ curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ TEST_ASSERT_EQUAL_STRING("application/json", ct);
+
+ TEST_ASSERT_EQUAL_STRING("{ \"id\": \"QkJCQkJCQkJCQkJCQkJCQkJCQkI=\" }", body.body);
+ body = (struct memory){0};
+
+ free(body.body);
+ curl_easy_cleanup(curl);
+ api_end();
+}
+
+void test_lookup_get() {
+ curl_global_init(CURL_GLOBAL_ALL);
+ CURLcode curlRes;
+ CURL *curl = curl_easy_init();
+ TEST_ASSERT_NOT_NULL(curl);
+
+ struct dht dht = {0};
+ dht.self = (struct nodeid){.inner={0x42424242, 0x42424242, 0x42424242, 0x42424242, 0x42424242}};
+ api_init(&dht);
+
+ curlRes = curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:6982/lookup");
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_memory);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ struct memory body = {0};
+ curlRes = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_perform(curl);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ char *ct;
+ curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ TEST_ASSERT_EQUAL_STRING("application/json", ct);
+
+ TEST_ASSERT_EQUAL_STRING(
+ "{ "
+ "\"state\": \"empty\" "
+ "}",
+ body.body
+ );
+ body = (struct memory){0};
+
+ {
+ curlRes = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_from_memory);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ struct memory req_body = {
+ .body = "{\"target\": \"BAAAAAAAAAAAAAAAAAAAAAAAAAA=\", \"state\": \"pending\"}",
+ .size = strlen(req_body.body),
+ };
+ curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_perform(curl);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+ TEST_ASSERT_EQUAL_STRING("application/json", ct);
+
+ TEST_ASSERT_EQUAL_STRING(
+ "{ "
+ "\"state\": \"pending\", "
+ "\"outstanding\": 0, "
+ "\"target\": \"BAAAAAAAAAAAAAAAAAAAAAAAAAA=\" "
+ "}",
+ body.body
+ );
+ body = (struct memory){0};
+ }
+
+ // The protocol does whatever and complete the lookup
+ dht.lookup.state = OP_COMPLETED;
+
+ {
+ curlRes = curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_from_memory);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ struct memory req_body = {
+ .body = "{\"state\": \"empty\"}",
+ .size = strlen(req_body.body),
+ };
+ curlRes = curl_easy_setopt(curl, CURLOPT_READDATA, &req_body);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_perform(curl);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+
+ curlRes = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
+ TEST_ASSERT_EQUAL(CURLE_OK, curlRes);
+ TEST_ASSERT_EQUAL_STRING("application/json", ct);
+
+ TEST_ASSERT_EQUAL_STRING(
+ "{ "
+ "\"state\": \"empty\" "
+ "}",
+ body.body
+ );
+ body = (struct memory){0};
+ }
+
+ free(body.body);
+ curl_easy_cleanup(curl);
+ curl_global_cleanup();
+ api_end();
+}
diff --git a/test/proto.c b/test/proto.c
index 29563a3..a566971 100644
--- a/test/proto.c
+++ b/test/proto.c
@@ -745,19 +745,21 @@ void test_lookup_response() {
// The node responds. This should add it to our frontier since it's
// empty. It should also fan out the search into what it returns since
// we still have empty spots after adding this one.
- char buff[] = "d1:y1:r1:t1:11:rd2:id20:CBBBBBBBBBBBBBBBBBBB5:nodes52:aBBBBBBBBBBBBBBBBBBB\xFF\xFF\xFF\xFF\x00\x01""aaaaBBBBBBBBBBBBBBBB\xFF\xFF\xFF\xFF\x00\x01""ee";
+ char buff[] = "d1:y1:r1:t1:11:rd2:id20:CBBBBBBBBBBBBBBBBBBB5:nodes78:aBBBBBBBBBBBBBBBBBBB\xFF\xFF\xFF\xFF\x00\x01""aaaaBBBBBBBBBBBBBBBB\xFF\xFF\xFF\xFF\x00\x01""aaaBBBBBBBBBBBBBBBBB\xFF\xFF\xFF\xFF\x00\x01""ee";
struct message* message_cursor = outbuff;
- int rc = proto_run(&dht, buff, sizeof(buff), (struct sockaddr_in*)&remote, sizeof(remote), now, &message_cursor, outbuff+2);
+ int rc = proto_run(&dht, buff, sizeof(buff), (struct sockaddr_in*)&remote, sizeof(remote), now, &message_cursor, outbuff+10);
TEST_ASSERT_EQUAL(rc, 0);
TEST_ASSERT_EQUAL_CHAR_ARRAY("CBBBBBBBBBBBBBBBBBBB", &dht.lookup.closest[0], 20);
- TEST_ASSERT_EQUAL_PTR(message_cursor, outbuff+2);
+ TEST_ASSERT_EQUAL_PTR(message_cursor, outbuff+3);
TEST_ASSERT_EQUAL(91, outbuff[0].payload_len);
TEST_ASSERT_EQUAL_CHAR_ARRAY("d1:ad2:id20:BBBBBBBBBBBBBBBBBBBB6:target20:aaaaaaaaaaaaaaaaaaaae1:q9:find_node1:t1:21:y1:qe", outbuff[0].payload, 91);
TEST_ASSERT_EQUAL(91, outbuff[1].payload_len);
TEST_ASSERT_EQUAL_CHAR_ARRAY("d1:ad2:id20:BBBBBBBBBBBBBBBBBBBB6:target20:aaaaaaaaaaaaaaaaaaaae1:q9:find_node1:t1:31:y1:qe", outbuff[1].payload, 91);
- TEST_ASSERT_EQUAL(2, dht.lookup.outstanding); // Resolve 1, add 2
+ TEST_ASSERT_EQUAL(91, outbuff[2].payload_len);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY("d1:ad2:id20:BBBBBBBBBBBBBBBBBBBB6:target20:aaaaaaaaaaaaaaaaaaaae1:q9:find_node1:t1:41:y1:qe", outbuff[2].payload, 91);
+ TEST_ASSERT_EQUAL(3, dht.lookup.outstanding); // Resolve 1, add 3
memcpy(&remote, &outbuff[0].dest, sizeof(remote));
}
@@ -770,20 +772,51 @@ void test_lookup_response() {
// we just asserted above. Those nodes happened to be closer to the final
// target than the outstanding request we have going on.
{
- for(size_t i = 0; i < 8; i++) {
- dht.lookup.closest[i] = (struct nodeid){.inner={0x42424242, 0x42424242, 0x42424242, 0x42424242, 0x42424242}};
- // The first two bytes match
- dht.lookup.closest[i].inner_b[0] = 'a';
- dht.lookup.closest[i].inner_b[1] = 'a';
- dht.lookup.closest[i].inner_b[2] = 'a' + i;
-
- dht.lookup.closest_addr[i].ip = 0;
- dht.lookup.closest_addr[i].port = 1;
- }
+ dht.lookup.closest[0] = (struct nodeid){.inner_b={"aaaBBBBBBBBBBBBBBBBB"}};
+ dht.lookup.closest_addr[0] = (struct addr){.ip = 0, .port = 1};
+ dht.lookup.closest[1] = (struct nodeid){.inner_b={"aabBBBBBBBBBBBBBBBBB"}};
+ dht.lookup.closest_addr[1] = (struct addr){.ip = 0, .port = 1};
+ dht.lookup.closest[2] = (struct nodeid){.inner_b={"aacBBBBBBBBBBBBBBBBB"}};
+ dht.lookup.closest_addr[2] = (struct addr){.ip = 0, .port = 1};
+ dht.lookup.closest[3] = (struct nodeid){.inner_b={"aadBBBBBBBBBBBBBBBBB"}};
+ dht.lookup.closest_addr[3] = (struct addr){.ip = 0, .port = 1};
+ dht.lookup.closest[4] = (struct nodeid){.inner_b={"aaeBBBBBBBBBBBBBBBBB"}};
+ dht.lookup.closest_addr[4] = (struct addr){.ip = 0, .port = 1};
+ dht.lookup.closest[5] = (struct nodeid){.inner_b={"aafBBBBBBBBBBBBBBBBB"}};
+ dht.lookup.closest_addr[5] = (struct addr){.ip = 0, .port = 1};
+ dht.lookup.closest[6] = (struct nodeid){.inner_b={"aagBBBBBBBBBBBBBBBBB"}};
+ dht.lookup.closest_addr[6] = (struct addr){.ip = 0, .port = 1};
+ dht.lookup.closest[7] = (struct nodeid){.inner_b={"aahBBBBBBBBBBBBBBBBB"}};
+ dht.lookup.closest_addr[7] = (struct addr){.ip = 0, .port = 1};
}
now += 1;
{
+ // The whole omitted interactive above included a response to the 3rd
+ // message from the last send, but before that, we fanned out from
+ // another node that knew about it too. This means we still have
+ // a leftover pending request to that node waiting for us. When that
+ // comes in, we should notice that, although it's better than some of
+ // our other candidates, it's also already there. It should therefore
+ // not be included
+ char buff[] = "d1:y1:r1:t1:41:rd2:id20:aaaBBBBBBBBBBBBBBBBB5:nodes0:ee";
+ struct message* message_cursor = outbuff;
+ int rc = proto_run(&dht, buff, sizeof(buff), (struct sockaddr_in*)&remote, sizeof(remote), now, &message_cursor, outbuff+2);
+
+ TEST_ASSERT_EQUAL(rc, 0);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[0], "aaaBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[1], "aabBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[2], "aacBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[3], "aadBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[4], "aaeBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[5], "aafBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[6], "aagBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[7], "aahBBBBBBBBBBBBBBBBB", 20);
+
+ TEST_ASSERT_EQUAL(2, dht.lookup.outstanding); // Resolve 1
+ }
+
+ {
// The node now finally responds, but woops only the first byte of its
// ID matches. That's worse than the frontier and shouldn't cause any
// addtional adjustment to the frontier.
@@ -794,10 +827,14 @@ void test_lookup_response() {
int rc = proto_run(&dht, buff, sizeof(buff), (struct sockaddr_in*)&remote, sizeof(remote), now, &message_cursor, outbuff+2);
TEST_ASSERT_EQUAL(rc, 0);
- // We only need to check this once since we always pick the first slot
- // with a given score. It's a little implementation dependant, but it
- // beats having 8 asserts.
TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[0], "aaaBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[1], "aabBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[2], "aacBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[3], "aadBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[4], "aaeBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[5], "aafBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[6], "aagBBBBBBBBBBBBBBBBB", 20);
+ TEST_ASSERT_EQUAL_CHAR_ARRAY(&dht.lookup.closest[7], "aahBBBBBBBBBBBBBBBBB", 20);
// And we didn't fan out to the new node since it's already part of the current frontier
TEST_ASSERT_EQUAL_PTR(message_cursor, outbuff);