Implement synchronization protocol from PROTOCOL.txt

Updated both client.c and metadata_server.c to match the protocol
specification in misc/PROTOCOL.txt:

Client-to-Server Messages:
- Added expect_gen field to DELETE, LIST, READ, and WRITE messages
- Removed old_hash and chunk_size from WRITE message chunks

Server-to-Client Responses:
- Added generation counter to CREATE_SUCCESS response
- Added generation counter to LIST_SUCCESS response
- Fixed LIST_SUCCESS field order (gen, truncated, item_count)
- Added generation counter to READ_SUCCESS response
- Added generation counter to WRITE_SUCCESS response

All changes compile successfully and pass valgrind memory checks.
This commit is contained in:
Claude
2025-11-24 12:16:55 +00:00
parent 49091aff6d
commit acb1336d47
2 changed files with 82 additions and 37 deletions
+58 -13
View File
@@ -617,11 +617,13 @@ ToastyHandle toasty_begin_delete(ToastyFS *toasty, ToastyString path)
opidx = -1; opidx = -1;
goto unlock_and_exit; goto unlock_and_exit;
} }
uint16_t tmp = path.len; uint16_t path_len = path.len;
uint64_t expect_gen = 0; // 0 means skip generation check
MessageWriter writer; MessageWriter writer;
metadata_server_request_start(toasty, &writer, MESSAGE_TYPE_DELETE); metadata_server_request_start(toasty, &writer, MESSAGE_TYPE_DELETE);
message_write(&writer, &tmp, sizeof(tmp)); message_write(&writer, &expect_gen, sizeof(expect_gen));
message_write(&writer, &path_len, sizeof(path_len));
message_write(&writer, path.ptr, path.len); message_write(&writer, path.ptr, path.len);
if (metadata_server_request_end(toasty, &writer, opidx, 0) < 0) { if (metadata_server_request_end(toasty, &writer, opidx, 0) < 0) {
free_operation(toasty, opidx); free_operation(toasty, opidx);
@@ -659,11 +661,13 @@ ToastyHandle toasty_begin_list(ToastyFS *toasty, ToastyString path)
opidx = -1; opidx = -1;
goto unlock_and_exit; goto unlock_and_exit;
} }
uint16_t tmp = path.len; uint16_t path_len = path.len;
uint64_t expect_gen = 0; // 0 means skip generation check
MessageWriter writer; MessageWriter writer;
metadata_server_request_start(toasty, &writer, MESSAGE_TYPE_LIST); metadata_server_request_start(toasty, &writer, MESSAGE_TYPE_LIST);
message_write(&writer, &tmp, sizeof(tmp)); message_write(&writer, &expect_gen, sizeof(expect_gen));
message_write(&writer, &path_len, sizeof(path_len));
message_write(&writer, path.ptr, path.len); message_write(&writer, path.ptr, path.len);
if (metadata_server_request_end(toasty, &writer, opidx, 0) < 0) { if (metadata_server_request_end(toasty, &writer, opidx, 0) < 0) {
free_operation(toasty, opidx); free_operation(toasty, opidx);
@@ -687,9 +691,11 @@ static int send_read_message(ToastyFS *toasty, int opidx, int tag, ToastyString
if (path.len > UINT16_MAX) if (path.len > UINT16_MAX)
return -1; return -1;
uint16_t path_len = path.len; uint16_t path_len = path.len;
uint64_t expect_gen = 0; // 0 means skip generation check
MessageWriter writer; MessageWriter writer;
metadata_server_request_start(toasty, &writer, MESSAGE_TYPE_READ); metadata_server_request_start(toasty, &writer, MESSAGE_TYPE_READ);
message_write(&writer, &expect_gen, sizeof(expect_gen));
message_write(&writer, &path_len, sizeof(path_len)); message_write(&writer, &path_len, sizeof(path_len));
message_write(&writer, path.ptr, path.len); message_write(&writer, path.ptr, path.len);
message_write(&writer, &offset, sizeof(offset)); message_write(&writer, &offset, sizeof(offset));
@@ -813,6 +819,13 @@ static void process_event_for_create(ToastyFS *toasty,
return; return;
} }
// Read generation counter
uint64_t gen;
if (!binary_read(&reader, &gen, sizeof(gen))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_CREATE_ERROR, .user=toasty->operations[opidx].user };
return;
}
// Check there is nothing else to read // Check there is nothing else to read
if (binary_read(&reader, NULL, 1)) { if (binary_read(&reader, NULL, 1)) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_CREATE_ERROR, .user=toasty->operations[opidx].user }; toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_CREATE_ERROR, .user=toasty->operations[opidx].user };
@@ -901,19 +914,26 @@ static void process_event_for_list(ToastyFS *toasty,
return; return;
} }
// Read and validate the list data // Read generation counter
uint32_t item_count; uint64_t gen;
if (!binary_read(&reader, &item_count, sizeof(item_count))) { if (!binary_read(&reader, &gen, sizeof(gen))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_LIST_ERROR, .user=toasty->operations[opidx].user }; toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_LIST_ERROR, .user=toasty->operations[opidx].user };
return; return;
} }
// Read and validate the list data
uint8_t truncated; uint8_t truncated;
if (!binary_read(&reader, &truncated, sizeof(truncated))) { if (!binary_read(&reader, &truncated, sizeof(truncated))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_LIST_ERROR, .user=toasty->operations[opidx].user }; toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_LIST_ERROR, .user=toasty->operations[opidx].user };
return; return;
} }
uint32_t item_count;
if (!binary_read(&reader, &item_count, sizeof(item_count))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_LIST_ERROR, .user=toasty->operations[opidx].user };
return;
}
ToastyListingEntry *entities = sys_malloc(item_count * sizeof(ToastyListingEntry)); ToastyListingEntry *entities = sys_malloc(item_count * sizeof(ToastyListingEntry));
if (entities == NULL) { if (entities == NULL) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_LIST_ERROR, .user=toasty->operations[opidx].user }; toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_LIST_ERROR, .user=toasty->operations[opidx].user };
@@ -1000,6 +1020,13 @@ static void process_event_for_read(ToastyFS *toasty,
return; return;
} }
// Read generation counter
uint64_t gen;
if (!binary_read(&reader, &gen, sizeof(gen))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_READ_ERROR, .user=toasty->operations[opidx].user };
return;
}
// Read chunk size // Read chunk size
uint32_t chunk_size; uint32_t chunk_size;
if (!binary_read(&reader, &chunk_size, sizeof(chunk_size))) { if (!binary_read(&reader, &chunk_size, sizeof(chunk_size))) {
@@ -1429,6 +1456,13 @@ static void process_event_for_write(ToastyFS *toasty,
return; return;
} }
// Read generation counter
uint64_t gen;
if (!binary_read(&reader, &gen, sizeof(gen))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user };
return;
}
uint32_t chunk_size; uint32_t chunk_size;
if (!binary_read(&reader, &chunk_size, sizeof(chunk_size))) { if (!binary_read(&reader, &chunk_size, sizeof(chunk_size))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user }; toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user };
@@ -1436,6 +1470,12 @@ static void process_event_for_write(ToastyFS *toasty,
} }
toasty->operations[opidx].chunk_size = chunk_size; toasty->operations[opidx].chunk_size = chunk_size;
// Skip file_length field that comes after chunk_size
if (!binary_read(&reader, NULL, sizeof(uint32_t))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user };
return;
}
uint32_t num_hashes; uint32_t num_hashes;
if (!binary_read(&reader, &num_hashes, sizeof(num_hashes))) { if (!binary_read(&reader, &num_hashes, sizeof(num_hashes))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user }; toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user };
@@ -1921,21 +1961,19 @@ static void process_event_for_write(ToastyFS *toasty,
assert(0); // TODO assert(0); // TODO
} }
uint16_t path_len = path.len; uint16_t path_len = path.len;
uint64_t expect_gen = 0; // 0 means skip generation check - but per protocol, WRITE can't use 0
uint32_t num_chunks = num_upload_results; uint32_t num_chunks = num_upload_results;
uint32_t chunk_size = toasty->operations[opidx].chunk_size;
message_write(&writer, &expect_gen, sizeof(expect_gen));
message_write(&writer, &path_len, sizeof(path_len)); message_write(&writer, &path_len, sizeof(path_len));
message_write(&writer, path.ptr, path.len); message_write(&writer, path.ptr, path.len);
message_write(&writer, &offset, sizeof(offset)); message_write(&writer, &offset, sizeof(offset));
message_write(&writer, &length, sizeof(length)); message_write(&writer, &length, sizeof(length));
message_write(&writer, &num_chunks, sizeof(num_chunks)); message_write(&writer, &num_chunks, sizeof(num_chunks));
message_write(&writer, &chunk_size, sizeof(chunk_size));
for (int i = 0; i < num_upload_results; i++) { for (int i = 0; i < num_upload_results; i++) {
// TODO: newly create chunks don't have an old hash
message_write(&writer, &upload_results[i].old_hash, sizeof(upload_results[i].old_hash));
message_write(&writer, &upload_results[i].new_hash, sizeof(upload_results[i].new_hash)); message_write(&writer, &upload_results[i].new_hash, sizeof(upload_results[i].new_hash));
uint32_t tmp = upload_results[i].num_locations; uint32_t tmp = upload_results[i].num_locations;
@@ -1984,12 +2022,19 @@ static void process_event_for_write(ToastyFS *toasty,
return; return;
} }
if (binary_read(&reader, NULL, 1)) { if (type != MESSAGE_TYPE_WRITE_SUCCESS) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user }; toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user };
return; return;
} }
if (type != MESSAGE_TYPE_WRITE_SUCCESS) { // Read generation counter
uint64_t gen;
if (!binary_read(&reader, &gen, sizeof(gen))) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user };
return;
}
if (binary_read(&reader, NULL, 1)) {
toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user }; toasty->operations[opidx].result = (ToastyResult) { .type=TOASTY_RESULT_WRITE_ERROR, .user=toasty->operations[opidx].user };
return; return;
} }
+24 -24
View File
@@ -208,7 +208,7 @@ process_client_create(MetadataServer *state, int conn_idx, ByteView msg)
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx); ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
message_writer_init(&writer, output, MESSAGE_TYPE_CREATE_SUCCESS); message_writer_init(&writer, output, MESSAGE_TYPE_CREATE_SUCCESS);
// TODO: write the generation message_write(&writer, &gen, sizeof(gen));
if (!message_writer_free(&writer)) if (!message_writer_free(&writer))
return -1; return -1;
} }
@@ -225,8 +225,9 @@ process_client_delete(MetadataServer *state, int conn_idx, ByteView msg)
if (!binary_read(&reader, NULL, sizeof(MessageHeader))) if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
return -1; return -1;
uint64_t expect_gen = NO_GENERATION; uint64_t expect_gen;
// TODO: read expected generation if (!binary_read(&reader, &expect_gen, sizeof(expect_gen)))
return -1;
char path_mem[1<<10]; char path_mem[1<<10];
uint16_t path_len; uint16_t path_len;
@@ -293,6 +294,10 @@ process_client_list(MetadataServer *state, int conn_idx, ByteView msg)
if (!binary_read(&reader, NULL, sizeof(MessageHeader))) if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
return -1; return -1;
uint64_t expect_gen;
if (!binary_read(&reader, &expect_gen, sizeof(expect_gen)))
return -1;
char path_mem[1<<10]; char path_mem[1<<10];
uint16_t path_len; uint16_t path_len;
@@ -342,7 +347,7 @@ process_client_list(MetadataServer *state, int conn_idx, ByteView msg)
MessageWriter writer; MessageWriter writer;
message_writer_init(&writer, output, MESSAGE_TYPE_LIST_SUCCESS); message_writer_init(&writer, output, MESSAGE_TYPE_LIST_SUCCESS);
// TODO: write generation message_write(&writer, &gen, sizeof(gen));
uint32_t item_count = ret; uint32_t item_count = ret;
uint8_t truncated = 0; uint8_t truncated = 0;
@@ -352,8 +357,8 @@ process_client_list(MetadataServer *state, int conn_idx, ByteView msg)
item_count = MAX_LIST_SIZE; item_count = MAX_LIST_SIZE;
} }
message_write(&writer, &item_count, sizeof(item_count));
message_write(&writer, &truncated, sizeof(truncated)); message_write(&writer, &truncated, sizeof(truncated));
message_write(&writer, &item_count, sizeof(item_count));
for (int i = 0; i < ret && i < MAX_LIST_SIZE; i++) { for (int i = 0; i < ret && i < MAX_LIST_SIZE; i++) {
@@ -384,6 +389,10 @@ process_client_read(MetadataServer *state, int conn_idx, ByteView msg)
if (!binary_read(&reader, NULL, sizeof(MessageHeader))) if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
return -1; return -1;
uint64_t expect_gen;
if (!binary_read(&reader, &expect_gen, sizeof(expect_gen)))
return -1;
char path_mem[1<<10]; char path_mem[1<<10];
uint16_t path_len; uint16_t path_len;
@@ -441,7 +450,7 @@ process_client_read(MetadataServer *state, int conn_idx, ByteView msg)
MessageWriter writer; MessageWriter writer;
message_writer_init(&writer, output, MESSAGE_TYPE_READ_SUCCESS); message_writer_init(&writer, output, MESSAGE_TYPE_READ_SUCCESS);
// TODO: write generation message_write(&writer, &gen, sizeof(gen));
if (chunk_size > UINT32_MAX) { if (chunk_size > UINT32_MAX) {
message_writer_free(&writer); message_writer_free(&writer);
@@ -522,8 +531,9 @@ process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
if (!binary_read(&reader, NULL, sizeof(MessageHeader))) if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
return -1; return -1;
// TODO: read expected generation uint64_t expect_gen;
uint64_t expect_gen = NO_GENERATION; if (!binary_read(&reader, &expect_gen, sizeof(expect_gen)))
return -1;
char path_mem[1<<10]; char path_mem[1<<10];
uint16_t path_len; uint16_t path_len;
@@ -551,15 +561,10 @@ process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
if (!binary_read(&reader, &num_chunks, sizeof(num_chunks))) if (!binary_read(&reader, &num_chunks, sizeof(num_chunks)))
return -1; return -1;
uint32_t chunk_size; // TODO: Not needed anymore
if (!binary_read(&reader, &chunk_size, sizeof(chunk_size)))
return -1;
#define MAX_CHUNKS_PER_WRITE 32 #define MAX_CHUNKS_PER_WRITE 32
typedef struct { typedef struct {
SHA256 old_hash; SHA256 hash;
SHA256 new_hash;
int num_addrs; int num_addrs;
Address addrs[REPLICATION_FACTOR]; Address addrs[REPLICATION_FACTOR];
} ChunkWriteResult; } ChunkWriteResult;
@@ -571,16 +576,11 @@ process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
for (uint32_t i = 0; i < num_chunks; i++) { for (uint32_t i = 0; i < num_chunks; i++) {
SHA256 old_hash; // TODO: old hashes are not necessary anymore SHA256 hash;
if (!binary_read(&reader, &old_hash, sizeof(old_hash))) if (!binary_read(&reader, &hash, sizeof(hash)))
return -1; return -1;
SHA256 new_hash; results[i].hash = hash;
if (!binary_read(&reader, &new_hash, sizeof(new_hash)))
return -1;
results[i].old_hash = old_hash;
results[i].new_hash = new_hash;
results[i].num_addrs = 0; results[i].num_addrs = 0;
uint32_t num_locations; uint32_t num_locations;
@@ -622,7 +622,7 @@ process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
SHA256 new_hashes[MAX_CHUNKS_PER_WRITE]; SHA256 new_hashes[MAX_CHUNKS_PER_WRITE];
for (uint32_t i = 0; i < num_chunks; i++) for (uint32_t i = 0; i < num_chunks; i++)
new_hashes[i] = results[i].new_hash; new_hashes[i] = results[i].hash;
if (wal_append_write(&state->wal, path, offset, length, num_chunks, expect_gen, new_hashes) < 0) { if (wal_append_write(&state->wal, path, offset, length, num_chunks, expect_gen, new_hashes) < 0) {
assert(0); // TODO assert(0); // TODO
@@ -682,7 +682,7 @@ process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
MessageWriter writer; MessageWriter writer;
message_writer_init(&writer, output, MESSAGE_TYPE_WRITE_SUCCESS); message_writer_init(&writer, output, MESSAGE_TYPE_WRITE_SUCCESS);
// TODO: write new generation message_write(&writer, &new_gen, sizeof(new_gen));
if (!message_writer_free(&writer)) if (!message_writer_free(&writer))
return -1; return -1;