Finish porting C tests
This commit is contained in:
parent
7fbf26bdec
commit
5ace7d1cc0
4 changed files with 424 additions and 172 deletions
|
@ -3,6 +3,7 @@
|
|||
members = [
|
||||
"automerge",
|
||||
"automerge-c",
|
||||
"automerge-c-v2",
|
||||
"automerge-backend",
|
||||
"automerge-backend-wasm",
|
||||
"automerge-frontend",
|
||||
|
|
|
@ -8,86 +8,98 @@
|
|||
|
||||
void test_sync_basic() {
|
||||
printf("begin sync test - basic\n");
|
||||
int len;
|
||||
|
||||
// In a real application you would need to check to make sure your buffer is large enough for any given read
|
||||
char buff[BUFSIZE];
|
||||
int ret;
|
||||
|
||||
Buffers rbuffs = automerge_create_buffs();
|
||||
Backend * dbA = automerge_init();
|
||||
Backend * dbB = automerge_init();
|
||||
|
||||
SyncState * ssA = automerge_sync_state_init();
|
||||
SyncState * ssB = automerge_sync_state_init();
|
||||
|
||||
len = automerge_generate_sync_message(dbA, ssA);
|
||||
// In a real application, we would use `len` to allocate `buff` here
|
||||
int len2 = automerge_read_binary(dbA, buff);
|
||||
automerge_receive_sync_message(dbB, ssB, buff, len);
|
||||
len = automerge_generate_sync_message(dbB, ssB);
|
||||
// No more sync messages were generated
|
||||
assert(len == 0);
|
||||
ret = automerge_generate_sync_message(dbA, &rbuffs, ssA);
|
||||
assert(ret == 0);
|
||||
ret = automerge_receive_sync_message(dbB, &rbuffs, ssB, rbuffs.data, rbuffs.lens[0]);
|
||||
assert(ret == 0);
|
||||
ret = automerge_generate_sync_message(dbB, &rbuffs, ssB);
|
||||
assert(ret == 0);
|
||||
assert(rbuffs.lens_len == 0);
|
||||
|
||||
automerge_sync_state_free(ssA);
|
||||
automerge_sync_state_free(ssB);
|
||||
automerge_free_buffs(&rbuffs);
|
||||
}
|
||||
|
||||
void test_sync_encode_decode() {
|
||||
printf("begin sync test - encode/decode\n");
|
||||
int len;
|
||||
int ret;
|
||||
|
||||
char buff[BUFSIZE];
|
||||
char sync_state_buff[BUFSIZE];
|
||||
|
||||
Buffers rbuffs = automerge_create_buffs();
|
||||
Backend * dbA = automerge_init();
|
||||
Backend * dbB = automerge_init();
|
||||
|
||||
const char * requestA1 = "{\"actor\":\"111111\",\"seq\":1,\"time\":0,\"deps\":[],\"startOp\":1,\"ops\":[{\"action\":\"set\",\"obj\":\"_root\",\"key\":\"bird\",\"value\":\"magpie\",\"pred\":[]}]}";
|
||||
const char * requestB1 = "{\"actor\":\"222222\",\"seq\":1,\"time\":0,\"deps\":[],\"startOp\":1,\"ops\":[{\"action\":\"set\",\"obj\":\"_root\",\"key\":\"bird\",\"value\":\"crow\",\"pred\":[]}]}";
|
||||
automerge_apply_local_change(dbA, requestA1);
|
||||
automerge_apply_local_change(dbB, requestB1);
|
||||
|
||||
SyncState * ssA = automerge_sync_state_init();
|
||||
SyncState * ssB = automerge_sync_state_init();
|
||||
|
||||
len = automerge_generate_sync_message(dbA, ssA);
|
||||
automerge_read_binary(dbA, buff);
|
||||
automerge_receive_sync_message(dbB, ssB, buff, len);
|
||||
const char * requestA1 = "{\"actor\":\"111111\",\"seq\":1,\"time\":0,\"deps\":[],\"startOp\":1,\"ops\":[{\"action\":\"set\",\"obj\":\"_root\",\"key\":\"bird\",\"value\":\"magpie\",\"pred\":[]}]}";
|
||||
const char * requestB1 = "{\"actor\":\"222222\",\"seq\":1,\"time\":0,\"deps\":[],\"startOp\":1,\"ops\":[{\"action\":\"set\",\"obj\":\"_root\",\"key\":\"bird\",\"value\":\"crow\",\"pred\":[]}]}";
|
||||
ret = automerge_apply_local_change(dbA, &rbuffs, requestA1);
|
||||
assert(ret == 0);
|
||||
ret = automerge_apply_local_change(dbB, &rbuffs, requestB1);
|
||||
assert(ret == 0);
|
||||
|
||||
len = automerge_generate_sync_message(dbB, ssB);
|
||||
automerge_read_binary(dbB, buff);
|
||||
automerge_receive_sync_message(dbA, ssA, buff, len);
|
||||
// A -> B
|
||||
ret = automerge_generate_sync_message(dbA, &rbuffs, ssA);
|
||||
assert(ret == 0);
|
||||
ret = automerge_receive_sync_message(dbB, &rbuffs, ssB, rbuffs.data, rbuffs.lens[0]);
|
||||
assert(ret == 0);
|
||||
|
||||
len = automerge_generate_sync_message(dbA, ssA);
|
||||
automerge_read_binary(dbA, buff);
|
||||
automerge_receive_sync_message(dbB, ssB, buff, len);
|
||||
// B -> A
|
||||
ret = automerge_generate_sync_message(dbB, &rbuffs, ssB);
|
||||
assert(ret == 0);
|
||||
ret = automerge_receive_sync_message(dbA, &rbuffs, ssA, rbuffs.data, rbuffs.lens[0]);
|
||||
assert(ret == 0);
|
||||
|
||||
// A -> B
|
||||
ret = automerge_generate_sync_message(dbA, &rbuffs, ssA);
|
||||
assert(ret == 0);
|
||||
ret = automerge_receive_sync_message(dbB, &rbuffs, ssB, rbuffs.data, rbuffs.lens[0]);
|
||||
assert(ret == 0);
|
||||
|
||||
len = automerge_generate_sync_message(dbB, ssB);
|
||||
automerge_read_binary(dbB, buff);
|
||||
automerge_receive_sync_message(dbA, ssA, buff, len);
|
||||
// B -> A
|
||||
ret = automerge_generate_sync_message(dbB, &rbuffs, ssB);
|
||||
assert(ret == 0);
|
||||
ret = automerge_receive_sync_message(dbA, &rbuffs, ssA, rbuffs.data, rbuffs.lens[0]);
|
||||
assert(ret == 0);
|
||||
|
||||
len = automerge_generate_sync_message(dbA, ssA);
|
||||
ret = automerge_generate_sync_message(dbA, &rbuffs, ssA);
|
||||
assert(ret == 0);
|
||||
|
||||
// Save the sync state
|
||||
int encoded_len = automerge_encode_sync_state(dbB, ssB);
|
||||
automerge_read_binary(dbB, sync_state_buff);
|
||||
ret = automerge_encode_sync_state(dbB, &rbuffs, ssB);
|
||||
assert(ret == 0);
|
||||
// Read it back
|
||||
ssB = automerge_decode_sync_state(sync_state_buff, encoded_len);
|
||||
ret = automerge_decode_sync_state(dbB, rbuffs.data, rbuffs.lens[0], &ssB);
|
||||
assert(ret == 0);
|
||||
|
||||
len = automerge_generate_sync_message(dbB, ssB);
|
||||
automerge_read_binary(dbB, buff);
|
||||
automerge_receive_sync_message(dbA, ssA, buff, len);
|
||||
// Redo B -> A
|
||||
ret = automerge_generate_sync_message(dbB, &rbuffs, ssB);
|
||||
assert(ret == 0);
|
||||
ret = automerge_receive_sync_message(dbA, &rbuffs, ssA, rbuffs.data, rbuffs.lens[0]);
|
||||
assert(ret == 0);
|
||||
|
||||
|
||||
len = automerge_generate_sync_message(dbA, ssA);
|
||||
assert(len == 0);
|
||||
ret = automerge_generate_sync_message(dbA, &rbuffs, ssA);
|
||||
assert(ret == 0);
|
||||
assert(rbuffs.lens_len == 0);
|
||||
}
|
||||
|
||||
void test_sync() {
|
||||
printf("begin sync test");
|
||||
test_sync_basic();
|
||||
test_sync_encode_decode();
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
int len;
|
||||
int ret;
|
||||
|
||||
// In a real application you would need to check to make sure your buffer is large enough for any given read
|
||||
char buff[BUFSIZE];
|
||||
|
@ -96,6 +108,7 @@ int main() {
|
|||
|
||||
printf("begin\n");
|
||||
|
||||
Buffers rbuffs = automerge_create_buffs();
|
||||
Backend * dbA = automerge_init();
|
||||
Backend * dbB = automerge_init();
|
||||
|
||||
|
@ -106,146 +119,148 @@ int main() {
|
|||
|
||||
printf("*** requestA1 ***\n\n%s\n\n",requestA1);
|
||||
|
||||
len = automerge_get_last_local_change(dbA);
|
||||
assert(len == -1);
|
||||
printf("*** last_local expected error string ** (%s)\n\n",automerge_error(dbA));
|
||||
|
||||
len = automerge_apply_local_change(dbA, requestA1);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbA, buff);
|
||||
ret = automerge_apply_local_change(dbA, &rbuffs, requestA1);
|
||||
assert(ret == 0);
|
||||
// 0th buff = the binary change, 1st buff = patch as JSON
|
||||
util_read_buffs_str(&rbuffs, 1, buff);
|
||||
printf("*** patchA1 ***\n\n%s\n\n",buff);
|
||||
|
||||
len = automerge_get_last_local_change(dbA);
|
||||
assert(len > 0);
|
||||
assert(len <= BUFSIZE);
|
||||
len = automerge_read_binary(dbA, buff);
|
||||
assert(len == 0);
|
||||
|
||||
len = automerge_apply_local_change(dbA, "{}");
|
||||
assert(len == -1);
|
||||
ret = automerge_apply_local_change(dbA, &rbuffs, "{}");
|
||||
assert(ret == -6);
|
||||
printf("*** patchA2 expected error string ** (%s)\n\n",automerge_error(dbA));
|
||||
|
||||
len = automerge_apply_local_change(dbA, requestA2);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbA, buff);
|
||||
ret = automerge_apply_local_change(dbA, &rbuffs, requestA2);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 1, buff);
|
||||
printf("*** patchA2 ***\n\n%s\n\n",buff);
|
||||
|
||||
len = automerge_apply_local_change(dbB, requestB1);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbB, buff);
|
||||
ret = automerge_apply_local_change(dbB, &rbuffs, requestB1);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 1, buff);
|
||||
printf("*** patchB1 ***\n\n%s\n\n",buff);
|
||||
|
||||
len = automerge_apply_local_change(dbB, requestB2);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbB, buff);
|
||||
ret = automerge_apply_local_change(dbB, &rbuffs, requestB2);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 1, buff);
|
||||
printf("*** patchB2 ***\n\n%s\n\n",buff);
|
||||
|
||||
printf("*** clone dbA -> dbC ***\n\n");
|
||||
Backend * dbC = automerge_clone(dbA);
|
||||
Backend * dbC = NULL;
|
||||
ret = automerge_clone(dbA, &dbC);
|
||||
assert(ret == 0);
|
||||
|
||||
len = automerge_get_patch(dbA);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbA, buff);
|
||||
len = automerge_get_patch(dbC);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbC, buff2);
|
||||
// the json can serialize in different orders so I can do a stright strcmp()
|
||||
ret = automerge_get_patch(dbA, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff);
|
||||
ret = automerge_get_patch(dbC, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff2);
|
||||
// the json can serialize in different orders so I can't do a straight strcmp()
|
||||
printf("*** get_patch of dbA & dbC -- equal? *** --> %s\n\n",strlen(buff) == strlen(buff2) ? "true" : "false");
|
||||
assert(strlen(buff) == strlen(buff2));
|
||||
|
||||
len = automerge_save(dbA);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_binary(dbA, buff2);
|
||||
printf("*** save dbA - %d bytes ***\n\n",len);
|
||||
ret = automerge_save(dbA, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs(&rbuffs, 0, buff2);
|
||||
printf("*** save dbA - %ld bytes ***\n\n", rbuffs.lens[0]);
|
||||
|
||||
printf("*** load the save into dbD ***\n\n");
|
||||
Backend * dbD = automerge_load(len, buff2);
|
||||
len = automerge_get_patch(dbD);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbD, buff2);
|
||||
Backend * dbD = automerge_load(buff2, rbuffs.lens[0]);
|
||||
ret = automerge_get_patch(dbD, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff2);
|
||||
printf("*** get_patch of dbA & dbD -- equal? *** --> %s\n\n",strlen(buff) == strlen(buff2) ? "true" : "false");
|
||||
assert(strlen(buff) == strlen(buff2));
|
||||
|
||||
printf("*** copy changes from dbA to B ***\n\n");
|
||||
len = automerge_get_changes_for_actor(dbA,"111111");
|
||||
while (len > 0) {
|
||||
assert(len <= BUFSIZE);
|
||||
int nextlen = automerge_read_binary(dbA,buff);
|
||||
automerge_write_change(dbB,len,buff);
|
||||
ret = automerge_get_changes_for_actor(dbA, &rbuffs, "111111");
|
||||
assert(ret == 0);
|
||||
|
||||
// decode the change for debug
|
||||
// encode and decode could happen with either dbA or dbB,
|
||||
// however encode needs to be done against dbB instead of dbA
|
||||
// only because dbA is in the middle of iterating over some binary results
|
||||
// and needs to finish before queuing another
|
||||
automerge_decode_change(dbA,len,buff);
|
||||
automerge_read_json(dbA, buff2);
|
||||
printf("Change decoded to json -- %s\n",buff2);
|
||||
automerge_encode_change(dbB,buff2);
|
||||
automerge_read_binary(dbB,buff3);
|
||||
assert(memcmp(buff,buff3,len) == 0);
|
||||
|
||||
len = nextlen;
|
||||
// We are reading one return value (rbuffs) while needing to return
|
||||
// something else, so we need another `Buffers` struct
|
||||
Buffers rbuffs2 = automerge_create_buffs();
|
||||
int start = 0;
|
||||
for(int i = 0; i < rbuffs.lens_len; ++i) {
|
||||
int len = rbuffs.lens[i];
|
||||
char * data_start = rbuffs.data + start;
|
||||
automerge_decode_change(dbA, &rbuffs2, data_start, len);
|
||||
util_read_buffs_str(&rbuffs2, 0, buff2);
|
||||
printf("Change decoded to json -- %s\n",buff2);
|
||||
start += len;
|
||||
automerge_encode_change(dbB, &rbuffs2, buff2);
|
||||
assert(memcmp(data_start, rbuffs2.data, len) == 0);
|
||||
}
|
||||
automerge_apply_changes(dbB);
|
||||
CBuffers cbuffs = { data: rbuffs.data, data_len: rbuffs.data_len, lens: rbuffs.lens, lens_len: rbuffs.lens_len };
|
||||
ret = automerge_apply_changes(dbB, &rbuffs, &cbuffs);
|
||||
assert(ret == 0);
|
||||
automerge_free_buffs(&rbuffs2);
|
||||
|
||||
printf("*** get head from dbB ***\n\n");
|
||||
ret = automerge_get_heads(dbB, &rbuffs);
|
||||
assert(ret == 0);
|
||||
|
||||
int num_heads = 0;
|
||||
len = automerge_get_heads(dbB);
|
||||
while (len > 0) {
|
||||
assert(len == 32);
|
||||
int nextlen = automerge_read_binary(dbB,buff3 + (num_heads * 32));
|
||||
num_heads++;
|
||||
len = nextlen;
|
||||
for (int i = 0; i < rbuffs.lens_len; ++i) {
|
||||
assert(rbuffs.lens[i] == 32);
|
||||
util_read_buffs(&rbuffs, i, buff3 + (num_heads * 32));
|
||||
num_heads++;
|
||||
}
|
||||
assert(num_heads == 2);
|
||||
len = automerge_get_changes(dbB,num_heads,buff3);
|
||||
assert(len == 0);
|
||||
ret = automerge_get_changes(dbB, &rbuffs, buff3, num_heads);
|
||||
assert(ret == 0);
|
||||
|
||||
printf("*** copy changes from dbB to A ***\n\n");
|
||||
len = automerge_get_changes_for_actor(dbB,"222222");
|
||||
while (len > 0) {
|
||||
assert(len <= BUFSIZE);
|
||||
int nextlen = automerge_read_binary(dbB,buff);
|
||||
automerge_write_change(dbA,len,buff);
|
||||
len = nextlen;
|
||||
}
|
||||
automerge_apply_changes(dbA);
|
||||
ret = automerge_get_changes_for_actor(dbB, &rbuffs, "222222");
|
||||
assert(ret == 0);
|
||||
|
||||
len = automerge_get_patch(dbA);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbA, buff);
|
||||
len = automerge_get_patch(dbB);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbB, buff2);
|
||||
CBuffers cbuffs2 = { data: rbuffs.data, data_len: rbuffs.data_len, lens: rbuffs.lens, lens_len: rbuffs.lens_len };
|
||||
ret = automerge_apply_changes(dbA, &rbuffs, &cbuffs2);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = automerge_get_patch(dbA, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff);
|
||||
ret = automerge_get_patch(dbB, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff2);
|
||||
// the json can serialize in different orders so I can't do a straight strcmp()
|
||||
printf("*** get_patch of dbA & dbB -- equal? *** --> %s\n\n",strlen(buff) == strlen(buff2) ? "true" : "false");
|
||||
assert(strlen(buff) == strlen(buff2));
|
||||
|
||||
printf("*** copy changes from dbA to E using load ***\n\n");
|
||||
Backend * dbE = automerge_init();
|
||||
len = automerge_get_changes(dbA,0,NULL);
|
||||
while (len > 0) {
|
||||
assert(len <= BUFSIZE);
|
||||
int nextlen = automerge_read_binary(dbA,buff);
|
||||
automerge_write_change(dbE,len,buff);
|
||||
len = nextlen;
|
||||
}
|
||||
automerge_load_changes(dbE);
|
||||
ret = automerge_get_changes(dbA, &rbuffs, NULL, 0);
|
||||
assert(ret == 0);
|
||||
CBuffers cbuffs3 = { data: rbuffs.data, data_len: rbuffs.data_len, lens: rbuffs.lens, lens_len: rbuffs.lens_len };
|
||||
ret = automerge_load_changes(dbE, &cbuffs3);
|
||||
assert(ret == 0);
|
||||
|
||||
len = automerge_get_patch(dbA);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbA, buff);
|
||||
len = automerge_get_patch(dbE);
|
||||
assert(len <= BUFSIZE);
|
||||
automerge_read_json(dbE, buff2);
|
||||
ret = automerge_get_patch(dbA, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff);
|
||||
ret = automerge_get_patch(dbE, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff2);
|
||||
// the json can serialize in different orders so I can't do a straight strcmp()
|
||||
printf("*** get_patch of dbA & dbE -- equal? *** --> %s\n\n",strlen(buff) == strlen(buff2) ? "true" : "false");
|
||||
assert(strlen(buff) == strlen(buff2));
|
||||
|
||||
len = automerge_get_missing_deps(dbE, num_heads, buff3);
|
||||
automerge_read_json(dbE, buff); // [] - nothing missing
|
||||
assert(strlen(buff) == 2);
|
||||
ret = automerge_get_patch(dbA, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff);
|
||||
ret = automerge_get_patch(dbB, &rbuffs);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff2);
|
||||
// the json can serialize in different orders so I can't do a straight strcmp()
|
||||
printf("*** get_patch of dbA & dbB -- equal? *** --> %s\n\n",strlen(buff) == strlen(buff2) ? "true" : "false");
|
||||
assert(strlen(buff) == strlen(buff2));
|
||||
|
||||
test_sync();
|
||||
ret = automerge_get_missing_deps(dbE, &rbuffs, buff3, num_heads);
|
||||
assert(ret == 0);
|
||||
util_read_buffs_str(&rbuffs, 0, buff);
|
||||
assert(strlen(buff) == 2); // [] - nothing missing
|
||||
|
||||
test_sync_basic();
|
||||
test_sync_encode_decode();
|
||||
|
||||
printf("free resources\n");
|
||||
automerge_free(dbA);
|
||||
|
@ -253,6 +268,7 @@ int main() {
|
|||
automerge_free(dbC);
|
||||
automerge_free(dbD);
|
||||
automerge_free(dbE);
|
||||
automerge_free_buffs(&rbuffs);
|
||||
|
||||
printf("end\n");
|
||||
}
|
||||
|
|
|
@ -8,8 +8,14 @@
|
|||
|
||||
typedef struct Backend Backend;
|
||||
|
||||
typedef struct SyncState SyncState;
|
||||
|
||||
/**
|
||||
* A byte buffer
|
||||
* A sequence of byte buffers that are contiguous in memory
|
||||
* The C caller allocates one of these with `create_buffs`
|
||||
* and passes it into each API call. This prevents allocating memory
|
||||
* on each call. The struct fields are just the constituent fields in a Vec
|
||||
* This is used for returning data to C.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
|
@ -17,17 +23,98 @@ typedef struct {
|
|||
*/
|
||||
uint8_t *data;
|
||||
/**
|
||||
* The number of bytes
|
||||
* The total number of bytes across all buffers
|
||||
*/
|
||||
uintptr_t data_len;
|
||||
} Buffer;
|
||||
/**
|
||||
* The total allocated memory `data` points to
|
||||
* This is needed so Rust can free `data`
|
||||
*/
|
||||
uintptr_t data_cap;
|
||||
/**
|
||||
* The length (in bytes) of each buffer
|
||||
*/
|
||||
uintptr_t *lens;
|
||||
/**
|
||||
* The number of buffers
|
||||
*/
|
||||
uintptr_t lens_len;
|
||||
/**
|
||||
* The total allocated memory `buf_lens` points to
|
||||
* This is needed so Rust can free `buf_lens`
|
||||
*/
|
||||
uintptr_t lens_cap;
|
||||
} Buffers;
|
||||
|
||||
/**
|
||||
* Similar to `Buffers`, except this struct
|
||||
* should be allocated / freed by C.
|
||||
* Used to pass an the C-equivalent of `Vec<Vec<u8>>` to Rust
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
uintptr_t data_len;
|
||||
uintptr_t *lens;
|
||||
uintptr_t lens_len;
|
||||
} CBuffers;
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This should be called with a valid backend pointer
|
||||
* and valid out pointers
|
||||
* This should be called with a valid pointer to a `Backend`
|
||||
* `CBuffers` should be non-null & have valid fields.
|
||||
*/
|
||||
intptr_t automerge_apply_local_change(Backend *backend, const uint8_t *request, uintptr_t len, Buffer **change_buf, Buffer **json_buf);
|
||||
intptr_t automerge_apply_changes(Backend *backend, Buffers *buffs, const CBuffers *cbuffs);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This should be called with a valid pointer to a `Backend`
|
||||
* and a valid pointer to a `Buffers``
|
||||
*/
|
||||
intptr_t automerge_apply_local_change(Backend *backend, Buffers *buffs, const char *request);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This should be called with a valid pointer to a `Backend`
|
||||
*/
|
||||
intptr_t automerge_clone(Backend *backend, Backend **new_);
|
||||
|
||||
/**
|
||||
* Create a `Buffers` struct to store return values
|
||||
*/
|
||||
Buffers automerge_create_buffs(void);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This must me called with a valid pointer to a change and the correct len
|
||||
*/
|
||||
intptr_t automerge_decode_change(Backend *backend, Buffers *buffs, const uint8_t *change, uintptr_t len);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* `encoded_state_[ptr|len]` must be the address & length of a byte array
|
||||
*/
|
||||
intptr_t automerge_decode_sync_state(Backend *backend,
|
||||
const uint8_t *encoded_state_ptr,
|
||||
uintptr_t encoded_state_len,
|
||||
SyncState **sync_state);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This must me called with a valid pointer to a JSON string of a change
|
||||
*/
|
||||
intptr_t automerge_encode_change(Backend *backend, Buffers *buffs, const char *change);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* Must be called with a pointer to a valid Backend, sync_state, and buffs
|
||||
*/
|
||||
intptr_t automerge_encode_sync_state(Backend *backend, Buffers *buffs, SyncState *sync_state);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This must be called with a valid backend pointer
|
||||
*/
|
||||
const char *automerge_error(Backend *backend);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
|
@ -35,6 +122,109 @@ intptr_t automerge_apply_local_change(Backend *backend, const uint8_t *request,
|
|||
*/
|
||||
void automerge_free(Backend *backend);
|
||||
|
||||
/**
|
||||
* Free the memory a `Buffers` struct points to
|
||||
*/
|
||||
intptr_t automerge_free_buffs(Buffers *buffs);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* Must be called with a valid backend pointer
|
||||
* sync_state must be a valid pointer to a SyncState
|
||||
* Returns an `isize` indicating the length of the binary message
|
||||
* (-1 if there was an error, 0 if there is no message)
|
||||
*/
|
||||
intptr_t automerge_generate_sync_message(Backend *backend, Buffers *buffs, SyncState *sync_state);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This must be called with a valid backend pointer,
|
||||
* binary must be a valid pointer to `hashes` hashes
|
||||
*/
|
||||
intptr_t automerge_get_changes(Backend *backend, Buffers *buffs, const uint8_t *bin, uintptr_t hashes);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This must be called with a valid pointer to a `Backend`
|
||||
* and a valid C String
|
||||
*/
|
||||
intptr_t automerge_get_changes_for_actor(Backend *backend, Buffers *buffs, const char *actor);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This must be called with a valid backend pointer
|
||||
*/
|
||||
intptr_t automerge_get_heads(Backend *backend, Buffers *buffs);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This must be called with a valid backend pointer,
|
||||
* binary must be a valid pointer to len bytes
|
||||
*/
|
||||
intptr_t automerge_get_missing_deps(Backend *backend, Buffers *buffs, const uint8_t *bin, uintptr_t len);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This should be called with a valid pointer to a `Backend`
|
||||
* and a valid pointer to a `Buffers``
|
||||
*/
|
||||
intptr_t automerge_get_patch(Backend *backend, Buffers *buffs);
|
||||
|
||||
Backend *automerge_init(void);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This must be called with a valid pointer to len bytes
|
||||
*/
|
||||
Backend *automerge_load(const uint8_t *data, uintptr_t len);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This should be called with a valid pointer to a `Backend`
|
||||
* and a valid pointers to a `CBuffers`
|
||||
*/
|
||||
intptr_t automerge_load_changes(Backend *backend, const CBuffers *cbuffs);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* Must be called with a valid backend pointer
|
||||
* sync_state must be a valid pointer to a SyncState
|
||||
* `encoded_msg_[ptr|len]` must be the address & length of a byte array
|
||||
*/
|
||||
intptr_t automerge_receive_sync_message(Backend *backend,
|
||||
Buffers *buffs,
|
||||
SyncState *sync_state,
|
||||
const uint8_t *encoded_msg_ptr,
|
||||
uintptr_t encoded_msg_len);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* This should be called with a valid pointer to a `Backend`
|
||||
*/
|
||||
intptr_t automerge_save(Backend *backend, Buffers *buffs);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* sync_state must be a valid pointer to a SyncState
|
||||
*/
|
||||
void automerge_sync_state_free(SyncState *sync_state);
|
||||
|
||||
SyncState *automerge_sync_state_init(void);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* Must be called with a valid buffs pointer
|
||||
* Copy a single buff from a Buffers to a destination.
|
||||
* The destination must be large enough to hold all of dest
|
||||
*/
|
||||
uintptr_t util_read_buffs(const Buffers *buffs, uintptr_t idx, uint8_t *dest);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* Must be called with a valid buffs pointer
|
||||
* Copy a single buff from a Buffers to a destination & null terminate it
|
||||
* The destination must be large enough to hold all of dest
|
||||
*/
|
||||
void util_read_buffs_str(Buffers *buffs, uintptr_t idx, uint8_t *dest);
|
||||
|
||||
#endif /* automerge_h */
|
||||
|
|
|
@ -128,7 +128,7 @@ impl Backend {
|
|||
}
|
||||
|
||||
fn handle_error(&mut self, err: CError) -> isize {
|
||||
let c_error = match CString::new(format!("{:?}", err)) {
|
||||
let c_error = match CString::new(format!("{}", err)) {
|
||||
Ok(e) => e,
|
||||
Err(_) => {
|
||||
return -1;
|
||||
|
@ -242,23 +242,25 @@ macro_rules! from_json {
|
|||
|
||||
/// Get hashes from a binary buffer
|
||||
macro_rules! get_hashes {
|
||||
($backend:expr, $bin:expr, $len:expr) => {{
|
||||
let slice = std::slice::from_raw_parts($bin, $len);
|
||||
let iter = slice.chunks_exact(32);
|
||||
let rem = iter.remainder().len();
|
||||
if rem > 0 {
|
||||
return $backend.handle_error(CError::BadHashes(format!(
|
||||
"Byte buffer had: {} leftover bytes",
|
||||
rem
|
||||
)));
|
||||
}
|
||||
let mut hashes = vec![];
|
||||
for chunk in iter {
|
||||
let hash: ChangeHash = match chunk.try_into() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return $backend.handle_error(CError::BadHashes(e.to_string())),
|
||||
};
|
||||
hashes.push(hash);
|
||||
($backend:expr, $bin:expr, $hashes:expr) => {{
|
||||
let mut hashes: Vec<ChangeHash> = vec![];
|
||||
if $hashes > 0 {
|
||||
let slice = std::slice::from_raw_parts($bin, 32 * $hashes);
|
||||
let iter = slice.chunks_exact(32);
|
||||
let rem = iter.remainder().len();
|
||||
if rem > 0 {
|
||||
return $backend.handle_error(CError::BadHashes(format!(
|
||||
"Byte buffer had: {} leftover bytes",
|
||||
rem
|
||||
)));
|
||||
}
|
||||
for chunk in iter {
|
||||
let hash: ChangeHash = match chunk.try_into() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return $backend.handle_error(CError::BadHashes(e.to_string())),
|
||||
};
|
||||
hashes.push(hash);
|
||||
}
|
||||
}
|
||||
hashes
|
||||
}};
|
||||
|
@ -337,6 +339,44 @@ pub unsafe extern "C" fn automerge_free_buffs(buffs: *mut Buffers) -> isize {
|
|||
0
|
||||
}
|
||||
|
||||
// util_* functions could be implemented in the parent language,
|
||||
// I have just written them in Rust b/c that's what I'm familiar with
|
||||
|
||||
/// # Safety
|
||||
/// Must be called with a valid buffs pointer
|
||||
/// Copy a single buff from a Buffers to a destination.
|
||||
/// The destination must be large enough to hold all of dest
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn util_read_buffs(
|
||||
buffs: *const Buffers,
|
||||
idx: usize,
|
||||
dest: *mut u8,
|
||||
) -> usize {
|
||||
let data = std::slice::from_raw_parts((*buffs).data, (*buffs).data_cap);
|
||||
let lens = std::slice::from_raw_parts((*buffs).lens, (*buffs).lens_cap);
|
||||
if idx >= (*buffs).lens_len {
|
||||
// This panic is ok b/c it indicates a truly unrecoverable error
|
||||
panic!("idx: {} out of range: {}", idx, (*buffs).lens_len);
|
||||
}
|
||||
let start: usize = lens.iter().take(idx).sum();
|
||||
let len = lens[idx];
|
||||
let end = start + len;
|
||||
let buff = &data[start..end];
|
||||
ptr::copy_nonoverlapping(buff.as_ptr(), dest, len);
|
||||
len
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// Must be called with a valid buffs pointer
|
||||
/// Copy a single buff from a Buffers to a destination & null terminate it
|
||||
/// The destination must be large enough to hold all of dest
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn util_read_buffs_str(buffs: *mut Buffers, idx: usize, dest: *mut u8) {
|
||||
let len = util_read_buffs(buffs, idx, dest);
|
||||
// null terminate
|
||||
*dest.add(len) = 0;
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This function should not fail insofar the fields of `buffs` are valid
|
||||
/// Write the contents of a `Vec<&[u8]>` to the `data` field of a `Buffers` struct
|
||||
|
@ -357,6 +397,7 @@ unsafe fn write_to_buffs(bytes: Vec<&[u8]>, buffs: &mut Buffers) {
|
|||
} else {
|
||||
get_buff_lens_vec!(buffs)
|
||||
});
|
||||
buf_lens.set_len(total_buffs);
|
||||
|
||||
let total_bytes: usize = bytes.iter().map(|b| b.len()).sum();
|
||||
let mut data: ManuallyDrop<Vec<u8>> = ManuallyDrop::new(if total_bytes > buffs.data_cap {
|
||||
|
@ -370,6 +411,8 @@ unsafe fn write_to_buffs(bytes: Vec<&[u8]>, buffs: &mut Buffers) {
|
|||
} else {
|
||||
get_data_vec!(buffs)
|
||||
});
|
||||
// Prevents out of bounds errors when writing to `data`
|
||||
data.set_len(total_bytes);
|
||||
|
||||
let mut start = 0;
|
||||
// Write `bytes` to `data` & update `buf_lens`
|
||||
|
@ -460,6 +503,7 @@ pub unsafe extern "C" fn automerge_save(backend: *mut Backend, buffs: *mut Buffe
|
|||
|
||||
/// # Safety
|
||||
/// This should be called with a valid pointer to a `Backend`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_clone(backend: *mut Backend, new: *mut *mut Backend) -> isize {
|
||||
let backend = get_backend_mut!(backend);
|
||||
(*new) = backend.clone().into();
|
||||
|
@ -547,6 +591,7 @@ pub unsafe extern "C" fn automerge_encode_change(
|
|||
|
||||
/// # Safety
|
||||
/// This must be called with a valid backend pointer
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_get_heads(backend: *mut Backend, buffs: *mut Buffers) -> isize {
|
||||
let backend = get_backend_mut!(backend);
|
||||
let buffs = get_buffs_mut!(buffs);
|
||||
|
@ -558,17 +603,17 @@ pub unsafe extern "C" fn automerge_get_heads(backend: *mut Backend, buffs: *mut
|
|||
|
||||
/// # Safety
|
||||
/// This must be called with a valid backend pointer,
|
||||
/// binary must be a valid pointer to len bytes
|
||||
/// binary must be a valid pointer to `hashes` hashes
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn automerge_get_changes(
|
||||
backend: *mut Backend,
|
||||
buffs: *mut Buffers,
|
||||
bin: *const u8,
|
||||
len: usize,
|
||||
hashes: usize,
|
||||
) -> isize {
|
||||
let backend = get_backend_mut!(backend);
|
||||
let buffs = get_buffs_mut!(buffs);
|
||||
let hashes = get_hashes!(backend, bin, len);
|
||||
let hashes = get_hashes!(backend, bin, hashes);
|
||||
let changes = backend.get_changes(&hashes);
|
||||
let bytes: Vec<_> = changes.into_iter().map(|c| c.raw_bytes()).collect();
|
||||
write_to_buffs(bytes, buffs);
|
||||
|
|
Loading…
Add table
Reference in a new issue