#include #include #include #include #include "sha256.h" #include "message.h" #include "file_system.h" #include "chunk_server.h" static void pending_download_list_init(PendingDownloadList *list) { list->count = 0; list->capacity = 0; list->items = NULL; } static void pending_download_list_free(PendingDownloadList *list) { free(list->items); } static int pending_download_list_add(PendingDownloadList *list, Address addr, SHA256 hash) { // Avoid duplicates for (int i = 0; i < list->count; i++) if (addr_eql(list->items[i].addr, addr) && !memcmp(&list->items[i].hash, &hash, sizeof(SHA256))) return 0; if (list->count == list->capacity) { int new_capacity; if (list->capacity == 0) new_capacity = 8; else new_capacity = 2 * list->capacity; PendingDownload *new_items = malloc(new_capacity * sizeof(PendingDownload)); if (new_items == NULL) return -1; if (list->capacity > 0) { memcpy(new_items, list->items, list->count * sizeof(list->items[0])); free(list->items); } list->items = new_items; list->capacity = new_capacity; } list->items[list->count++] = (PendingDownload) { addr, hash }; return 0; } static int chunk_store_init(ChunkStore *store, string path) { if (create_dir(path) && errno != EEXIST) return -1; if (get_full_path(path, store->path) < 0) return -1; return 0; } static void chunk_store_free(ChunkStore *store) { (void) store; } static void append_hex_as_str(char *out, SHA256 hash) { char table[] = "0123456789abcdef"; for (int i = 0; i < (int) sizeof(hash); i++) { out[(i << 1) + 0] = table[hash.data[i] >> 4]; out[(i << 1) + 1] = table[hash.data[i] & 0xF]; } } static string hash2path(ChunkStore *store, SHA256 hash, char *out) { strcpy(out, store->path); strcat(out, "/"); size_t tmp = strlen(out); append_hex_as_str(out + tmp, hash); out[tmp + 64] = '\0'; return (string) { out, strlen(out) }; } static int load_chunk(ChunkStore *store, SHA256 hash, string *data) { char buf[PATH_MAX]; string path = hash2path(store, hash, buf); return file_read_all(path, data); } static int store_chunk(ChunkStore *store, string data, SHA256 *hash) { sha256(data.ptr, data.len, (uint8_t*) hash->data); char buf[PATH_MAX]; string path = hash2path(store, *hash, buf); return file_write_atomic(path, data); } static int chunk_store_get(ChunkStore *store, SHA256 hash, string *data) { return load_chunk(store, hash, data); } static int chunk_store_add(ChunkStore *store, string data) { SHA256 dummy; return store_chunk(store, data, &dummy); } static void chunk_store_remove(ChunkStore *store, SHA256 hash) { char buf[PATH_MAX]; string path = hash2path(store, hash, buf); remove_file_or_dir(path); } static int chunk_store_patch(ChunkStore *store, SHA256 target_chunk, uint64_t patch_off, string patch, SHA256 *new_hash) { string data; int ret = load_chunk(store, target_chunk, &data); if (ret < 0) return -1; if (patch_off > SIZE_MAX - patch.len) { free(data.ptr); return -1; } if (patch_off + (size_t) patch.len > (size_t) data.len) { free(data.ptr); return -1; } memcpy(data.ptr + patch_off, patch.ptr, patch.len); ret = store_chunk(store, data, new_hash); if (ret < 0) { free(data.ptr); return -1; } free(data.ptr); return 0; } static int send_error(TCP *tcp, int conn_idx, bool close, uint16_t type, string msg) { MessageWriter writer; ByteQueue *output = tcp_output_buffer(tcp, conn_idx); message_writer_init(&writer, output, type); uint16_t len = MIN(msg.len, UINT16_MAX); message_write(&writer, &len, sizeof(len)); message_write(&writer, msg.ptr, len); if (!message_writer_free(&writer)) return -1; if (close) return -1; return 0; } static void start_download_if_necessary(ChunkServer *state) { if (state->pending_download_list.count == 0 || state->downloading) return; ByteQueue *output; if (tcp_connect(&state->tcp, state->pending_download_list.items[0].addr, TAG_CHUNK_SERVER, &output) < 0) { // Failed to connect, remove this download from the list and try next time if (state->pending_download_list.count > 1) { memmove(&state->pending_download_list.items[0], &state->pending_download_list.items[1], (state->pending_download_list.count - 1) * sizeof(PendingDownload)); } state->pending_download_list.count--; return; } state->downloading = true; MessageWriter writer; message_writer_init(&writer, output, MESSAGE_TYPE_DOWNLOAD_CHUNK); // Write the hash of the chunk to download message_write(&writer, &state->pending_download_list.items[0].hash, sizeof(state->pending_download_list.items[0].hash)); // Request the entire chunk: offset = 0 uint32_t offset = 0; message_write(&writer, &offset, sizeof(offset)); // Request maximum reasonable chunk size (64MB) uint32_t length = 64 * 1024 * 1024; message_write(&writer, &length, sizeof(length)); if (!message_writer_free(&writer)) { // Failed to send message, close connection and retry state->downloading = false; return; } } static int process_metadata_server_state_update(ChunkServer *state, int conn_idx, ByteView msg) { uint32_t add_count; uint32_t rem_count; BinaryReader reader = { msg.ptr, msg.len, 0 }; // Read header if (!binary_read(&reader, NULL, sizeof(MessageHeader))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message")); if (!binary_read(&reader, &add_count, sizeof(add_count))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message")); if (!binary_read(&reader, &rem_count, sizeof(rem_count))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message")); SHA256 *add_list = malloc(add_count * sizeof(SHA256)); SHA256 *rem_list = malloc(rem_count * sizeof(SHA256)); if (add_list == NULL || rem_list == NULL) { free(add_list); free(rem_list); return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Out of memory")); } for (uint32_t i = 0; i < add_count; i++) { if (!binary_read(&reader, &add_list[i], sizeof(SHA256))) { free(add_list); free(rem_list); return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message")); } } for (uint32_t i = 0; i < rem_count; i++) { if (!binary_read(&reader, &rem_list[i], sizeof(SHA256))) { free(add_list); free(rem_list); return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message")); } } if (binary_read(&reader, NULL, 1)) { free(add_list); free(rem_list); return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message")); } // Process the state update: // 1. Move chunks in rem_list from main to orphaned directory (mark for deletion) // 2. Move chunks in add_list from orphaned to main directory (unmark for deletion) // 3. Check that all chunks in add_list exist SHA256 *missing_chunks = NULL; uint32_t missing_count = 0; // Process add_list: ensure chunks exist and move from orphaned if needed for (uint32_t i = 0; i < add_count; i++) { char main_path[PATH_MAX]; char orphaned_path[PATH_MAX]; // Get paths for main and orphaned locations hash2path(&state->store, add_list[i], main_path); snprintf(orphaned_path, sizeof(orphaned_path), "%s/orphaned/", state->store.path); string orphaned_dir = { orphaned_path, strlen(orphaned_path) }; string orphaned_file = hash2path(&state->store, add_list[i], orphaned_path); orphaned_file.ptr = orphaned_path; // Build orphaned path properly strcpy(orphaned_path, state->store.path); strcat(orphaned_path, "/orphaned/"); size_t tmp = strlen(orphaned_path); append_hex_as_str(orphaned_path + tmp, add_list[i]); orphaned_path[tmp + 64] = '\0'; // Check if chunk exists in main directory Handle fd; if (file_open((string) { main_path, strlen(main_path) }, &fd) == 0) { file_close(fd); // Chunk is in main directory, nothing to do } else if (file_open((string) { orphaned_path, strlen(orphaned_path) }, &fd) == 0) { file_close(fd); // Chunk is in orphaned directory, move it back to main if (rename_file_or_dir((string) { orphaned_path, strlen(orphaned_path) }, (string) { main_path, strlen(main_path) }) < 0) { // Failed to move, treat as missing if (missing_chunks == NULL) missing_chunks = malloc(add_count * sizeof(SHA256)); if (missing_chunks) missing_chunks[missing_count++] = add_list[i]; } } else { // Chunk is missing in both locations if (missing_chunks == NULL) missing_chunks = malloc(add_count * sizeof(SHA256)); if (missing_chunks) missing_chunks[missing_count++] = add_list[i]; } } // Process rem_list: move chunks from main to orphaned directory // First ensure orphaned directory exists char orphaned_dir_path[PATH_MAX]; snprintf(orphaned_dir_path, sizeof(orphaned_dir_path), "%s/orphaned", state->store.path); create_dir((string) { orphaned_dir_path, strlen(orphaned_dir_path) }); for (uint32_t i = 0; i < rem_count; i++) { char main_path[PATH_MAX]; char orphaned_path[PATH_MAX]; hash2path(&state->store, rem_list[i], main_path); strcpy(orphaned_path, state->store.path); strcat(orphaned_path, "/orphaned/"); size_t tmp = strlen(orphaned_path); append_hex_as_str(orphaned_path + tmp, rem_list[i]); orphaned_path[tmp + 64] = '\0'; // Move from main to orphaned (ignore errors, chunk might not exist) rename_file_or_dir((string) { main_path, strlen(main_path) }, (string) { orphaned_path, strlen(orphaned_path) }); } free(add_list); free(rem_list); // Send response if (missing_count > 0) { // Send error with list of missing chunks ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx); MessageWriter writer; message_writer_init(&writer, output, MESSAGE_TYPE_STATE_UPDATE_ERROR); uint16_t error_len = 15; // "Missing chunks" message_write(&writer, &error_len, sizeof(error_len)); message_write(&writer, "Missing chunks", error_len); message_write(&writer, &missing_count, sizeof(missing_count)); for (uint32_t i = 0; i < missing_count; i++) message_write(&writer, &missing_chunks[i], sizeof(SHA256)); free(missing_chunks); if (!message_writer_free(&writer)) return -1; } else { // Send success ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx); MessageWriter writer; message_writer_init(&writer, output, MESSAGE_TYPE_STATE_UPDATE_SUCCESS); if (!message_writer_free(&writer)) return -1; } return 0; } static int process_metadata_server_download_locations(ChunkServer *state, int conn_idx, ByteView msg) { (void) conn_idx; // The metadata server wants us to download chunks from other chunk servers BinaryReader reader = { msg.ptr, msg.len, 0 }; // Read header if (!binary_read(&reader, NULL, sizeof(MessageHeader))) return -1; // The message layout is this: // // struct IPv4Pair { // IPv4 addr; // uint16_t port; // } // // struct IPv6Pair { // IPv6 addr; // uint16_t port; // } // // struct AddressList { // uint8_t num_ipv4; // uint8_t num_ipv6; // IPv4Pair ipv4[num_ipv4]; // IPv6Pair ipv6[num_ipv6]; // } // // struct Group { // AddressList address_list; // uint32_t num_hashes; // SHA256 hashes[num_hashes]; // } // // struct Message { // uint16_t num_groups; // Group groups[num_groups] // } uint16_t num_groups; if (binary_read(&reader, &num_groups, sizeof(num_groups))) return -1; for (uint16_t i = 0; i < num_groups; i++) { uint8_t num_ipv4; if (binary_read(&reader, &num_ipv4, sizeof(num_ipv4))) return -1; uint8_t num_ipv6; if (binary_read(&reader, &num_ipv6, sizeof(num_ipv6))) return -1; IPv4 ipv4[UINT8_MAX]; IPv6 ipv6[UINT8_MAX]; uint8_t ipv4_port[UINT8_MAX]; uint16_t ipv6_port[UINT8_MAX]; for (uint8_t j = 0; j < num_ipv4; j++) { if (binary_read(&reader, &ipv4[i], sizeof(ipv4[i]))) return -1; if (binary_read(&reader, &ipv4_port[i], sizeof(ipv4_port[i]))) return -1; } for (uint8_t j = 0; j < num_ipv6; j++) { if (binary_read(&reader, &ipv6[i], sizeof(ipv6[i]))) return -1; if (binary_read(&reader, &ipv6_port[i], sizeof(ipv6_port[i]))) return -1; } uint32_t num_hashes; if (binary_read(&reader, &num_hashes, sizeof(num_hashes))) return -1; for (uint32_t j = 0; j < num_hashes; j++) { SHA256 hash; if (binary_read(&reader, &hash, sizeof(hash))) return -1; for (uint8_t k = 0; k < num_ipv4; k++) pending_download_list_add( &state->pending_download_list, (Address) { .is_ipv4=true, .ipv4=ipv4[k], .port=ipv4_port[i] }, hash ); for (uint8_t k = 0; k < num_ipv6; k++) pending_download_list_add( &state->pending_download_list, (Address) { .is_ipv4=false, .ipv6=ipv6[k], .port=ipv6_port[i] }, hash ); } } if (binary_read(&reader, NULL, 1)) return -1; start_download_if_necessary(state); // There is no need to respond here return 0; } static int process_metadata_server_message(ChunkServer *state, int conn_idx, uint16_t type, ByteView msg) { switch (type) { case MESSAGE_TYPE_STATE_UPDATE: return process_metadata_server_state_update(state, conn_idx, msg); case MESSAGE_TYPE_DOWNLOAD_LOCATIONS: return process_metadata_server_download_locations(state, conn_idx, msg); } return -1; } static int process_chunk_server_download_error(ChunkServer *state, int conn_idx, ByteView msg) { (void) msg; (void) conn_idx; // Download failed, mark as not downloading and remove the failed item state->downloading = false; if (state->pending_download_list.count > 0) { // Remove the first item (the one that failed) if (state->pending_download_list.count > 1) { memmove(&state->pending_download_list.items[0], &state->pending_download_list.items[1], (state->pending_download_list.count - 1) * sizeof(PendingDownload)); } state->pending_download_list.count--; } // Try next download if any pending start_download_if_necessary(state); return 0; } static int process_chunk_server_download_success(ChunkServer *state, int conn_idx, ByteView msg) { (void) conn_idx; BinaryReader reader = { msg.ptr, msg.len, 0 }; // Read header if (!binary_read(&reader, NULL, sizeof(MessageHeader))) return -1; // Read data length uint32_t data_len; if (!binary_read(&reader, &data_len, sizeof(data_len))) return -1; // Read the chunk data if (reader.cur + data_len > reader.len) return -1; string data = { reader.src + reader.cur, data_len }; // Store the downloaded chunk if (chunk_store_add(&state->store, data) < 0) { // Failed to store, treat as error state->downloading = false; if (state->pending_download_list.count > 0) { if (state->pending_download_list.count > 1) { memmove(&state->pending_download_list.items[0], &state->pending_download_list.items[1], (state->pending_download_list.count - 1) * sizeof(PendingDownload)); } state->pending_download_list.count--; } start_download_if_necessary(state); return 0; } // Download succeeded, mark as not downloading and remove the completed item state->downloading = false; if (state->pending_download_list.count > 0) { // Remove the first item (the one that succeeded) if (state->pending_download_list.count > 1) { memmove(&state->pending_download_list.items[0], &state->pending_download_list.items[1], (state->pending_download_list.count - 1) * sizeof(PendingDownload)); } state->pending_download_list.count--; } // Try next download if any pending start_download_if_necessary(state); return 0; } static int process_chunk_server_message(ChunkServer *state, int conn_idx, uint16_t msg_type, ByteView msg) { switch (msg_type) { case MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR: return process_chunk_server_download_error(state, conn_idx, msg); case MESSAGE_TYPE_DOWNLOAD_CHUNK_SUCCESS: return process_chunk_server_download_success(state, conn_idx, msg); } return -1; } static int process_client_create_chunk(ChunkServer *state, int conn_idx, ByteView msg) { BinaryReader reader = { msg.ptr, msg.len, 0 }; // Read header if (!binary_read(&reader, NULL, sizeof(MessageHeader))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message")); uint32_t chunk_size; if (!binary_read(&reader, &chunk_size, sizeof(chunk_size))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message")); uint32_t target_off; if (!binary_read(&reader, &target_off, sizeof(target_off))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message")); uint32_t target_len; if (!binary_read(&reader, &target_len, sizeof(target_len))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message")); string data = { reader.src + reader.cur, target_len }; if (!binary_read(&reader, NULL, target_len)) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message")); // Check that there are no more bytes to read if (binary_read(&reader, NULL, 1)) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message")); char *mem = malloc(chunk_size); if (mem == NULL) return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Out of memory")); assert(target_off + data.len <= chunk_size); memset(mem, 0, chunk_size); memcpy(mem + target_off, data.ptr, data.len); SHA256 new_hash; sha256(mem, chunk_size, (uint8_t*) new_hash.data); int ret = chunk_store_add(&state->store, (string) { mem, chunk_size }); free(mem); if (ret < 0) return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("I/O error")); MessageWriter writer; ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx); message_writer_init(&writer, output, MESSAGE_TYPE_CREATE_CHUNK_SUCCESS); message_write(&writer, &new_hash, sizeof(new_hash)); if (!message_writer_free(&writer)) return -1; return 0; } static int process_client_upload_chunk(ChunkServer *state, int conn_idx, ByteView msg) { BinaryReader reader = { msg.ptr, msg.len, 0 }; // Read header if (!binary_read(&reader, NULL, sizeof(MessageHeader))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message")); SHA256 target_hash; if (!binary_read(&reader, &target_hash, sizeof(target_hash))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message")); uint32_t target_off; if (!binary_read(&reader, &target_off, sizeof(target_off))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message")); uint32_t data_len; if (!binary_read(&reader, &data_len, sizeof(data_len))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message")); string data = { reader.src + reader.cur, data_len }; // Check that there are no more bytes to read if (binary_read(&reader, NULL, 1)) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message")); SHA256 new_hash; int ret = chunk_store_patch(&state->store, target_hash, target_off, data, &new_hash); if (ret < 0) return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("I/O error")); MessageWriter writer; ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx); message_writer_init(&writer, output, MESSAGE_TYPE_UPLOAD_CHUNK_SUCCESS); if (!message_writer_free(&writer)) return -1; return 0; } static int process_client_download_chunk(ChunkServer *state, int conn_idx, ByteView msg) { BinaryReader reader = { msg.ptr, msg.len, 0 }; // Read header if (!binary_read(&reader, NULL, sizeof(MessageHeader))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message")); SHA256 target_hash; if (!binary_read(&reader, &target_hash, sizeof(target_hash))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message")); uint32_t target_off; if (!binary_read(&reader, &target_off, sizeof(target_off))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message")); uint32_t target_len; if (!binary_read(&reader, &target_len, sizeof(target_len))) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message")); // Check that there are no more bytes to read if (binary_read(&reader, NULL, 1)) return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message")); string data; int ret = chunk_store_get(&state->store, target_hash, &data); if (ret < 0) return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("I/O error")); if (target_off >= (size_t) data.len || target_len > (size_t) data.len - target_off) { free(data.ptr); return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid range")); } string slice = { data.ptr + target_off, target_len }; MessageWriter writer; ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx); message_writer_init(&writer, output, MESSAGE_TYPE_DOWNLOAD_CHUNK_SUCCESS); message_write(&writer, &target_len, sizeof(target_len)); message_write(&writer, slice.ptr, slice.len); free(data.ptr); if (!message_writer_free(&writer)) return -1; return 0; } static int process_client_message(ChunkServer *state, int conn_idx, uint16_t type, ByteView msg) { switch (type) { case MESSAGE_TYPE_CREATE_CHUNK: return process_client_create_chunk(state, conn_idx, msg); case MESSAGE_TYPE_UPLOAD_CHUNK: return process_client_upload_chunk(state, conn_idx, msg); case MESSAGE_TYPE_DOWNLOAD_CHUNK: return process_client_download_chunk(state, conn_idx, msg); default:break; } return -1; } int chunk_server_init(ChunkServer *state, int argc, char **argv) { (void) argc; (void) argv; char addr[] = "127.0.0.1"; uint16_t port = 8080; string path = S("chunk_server_data_0/"); char metadata_server_addr[] = "127.0.0.1"; uint16_t metadata_server_port = 8081; tcp_context_init(&state->tcp); int ret = tcp_listen(&state->tcp, addr, port); if (ret < 0) { tcp_context_free(&state->tcp); return -1; } ret = chunk_store_init(&state->store, path); if (ret < 0) { tcp_context_free(&state->tcp); return -1; } state->downloading = false; pending_download_list_init(&state->pending_download_list); // Initialize metadata server address // // TODO: This should also support IPv6 state->metadata_server_addr.is_ipv4 = true; if (inet_pton(AF_INET, metadata_server_addr, &state->metadata_server_addr.ipv4) != 1) { tcp_context_free(&state->tcp); chunk_store_free(&state->store); return -1; } state->metadata_server_addr.port = metadata_server_port; state->metadata_server_disconnect_time = 0; return 0; } int chunk_server_free(ChunkServer *state) { pending_download_list_free(&state->pending_download_list); chunk_store_free(&state->store); tcp_context_free(&state->tcp); return 0; } int chunk_server_step(ChunkServer *state) { Event events[MAX_CONNS+1]; int num_events = tcp_process_events(&state->tcp, events); Time current_time = get_current_time(); if (current_time == INVALID_TIME) return -1; for (int i = 0; i < num_events; i++) { int conn_idx = events[i].conn_idx; switch (events[i].type) { case EVENT_CONNECT: if (tcp_get_tag(&state->tcp, conn_idx) == TAG_METADATA_SERVER) state->metadata_server_disconnect_time = 0; break; case EVENT_DISCONNECT: switch (tcp_get_tag(&state->tcp, conn_idx)) { case TAG_METADATA_SERVER: state->metadata_server_disconnect_time = current_time; break; case TAG_CHUNK_SERVER: // Connection to chunk server disconnected during download if (state->downloading) { // Mark as not downloading and retry state->downloading = false; // The current download item will be retried on next call // to start_download_if_necessary } break; } break; case EVENT_MESSAGE: { for (;;) { ByteView msg; uint16_t msg_type; int ret = tcp_next_message(&state->tcp, conn_idx, &msg, &msg_type); if (ret == 0) break; if (ret < 0) { tcp_close(&state->tcp, conn_idx); break; } switch (tcp_get_tag(&state->tcp, conn_idx)) { case TAG_METADATA_SERVER: ret = process_metadata_server_message(state, conn_idx, msg_type, msg); break; case TAG_CHUNK_SERVER: ret = process_chunk_server_message(state, conn_idx, msg_type, msg); break; default: ret = process_client_message(state, conn_idx, msg_type, msg); break; } if (ret < 0) { tcp_close(&state->tcp, conn_idx); break; } tcp_consume_message(&state->tcp, conn_idx); } } break; } } // TODO: periodically look for chunks that have their hashes messed up and delete them // TODO: periodically start downloads if some are pending and weren't started yet // start_download_if_necessary(state); if (state->metadata_server_disconnect_time > 0 && current_time - state->metadata_server_disconnect_time > CHUNK_SERVER_RECONNECT_TIME) { ByteQueue *output; if (tcp_connect(&state->tcp, state->metadata_server_addr, TAG_METADATA_SERVER, &output) < 0) state->metadata_server_disconnect_time = current_time; else { state->metadata_server_disconnect_time = 0; // Send AUTH message to authenticate with metadata server MessageWriter writer; message_writer_init(&writer, output, MESSAGE_TYPE_AUTH); // Send our listening address(es) // For now, we only support IPv4 (as noted in program_init) uint32_t num_ipv4 = 1; message_write(&writer, &num_ipv4, sizeof(num_ipv4)); // Write our IPv4 address and port IPv4 our_ipv4; if (inet_pton(AF_INET, "127.0.0.1", &our_ipv4) == 1) { message_write(&writer, &our_ipv4, sizeof(our_ipv4)); uint16_t our_port = 8080; // From program_init message_write(&writer, &our_port, sizeof(our_port)); } else { // Failed to parse our address, send 0 IPv4s num_ipv4 = 0; // We already wrote 1, this is an error case // For now, continue with the bad data } // No IPv6 addresses for now uint32_t num_ipv6 = 0; message_write(&writer, &num_ipv6, sizeof(num_ipv6)); if (!message_writer_free(&writer)) { // Failed to send AUTH, will retry on next reconnect state->metadata_server_disconnect_time = current_time; } } } return 0; }