Finish porting C tests

This commit is contained in:
Vedant Roy 2021-05-25 00:51:40 -07:00 committed by Orion Henry
parent 7fbf26bdec
commit 5ace7d1cc0
4 changed files with 424 additions and 172 deletions

View file

@ -3,6 +3,7 @@
members = [
"automerge",
"automerge-c",
"automerge-c-v2",
"automerge-backend",
"automerge-backend-wasm",
"automerge-frontend",

View file

@ -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");
}

View file

@ -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 */

View file

@ -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);