diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2021-06-04 11:22:13 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2021-06-04 14:09:36 +0200 |
commit | 9478f48e6b692d06c83126e93b77ce581cf979bf (patch) | |
tree | e2e876ac0537c409441bcb99a32f358dc333e243 | |
parent | 652d0efdbb66d60d1a56691c2f0418ac5e2fca83 (diff) |
openpgp-ffi: Implement pgp_writer_alloc_with_capacity.
- This provides an alternative to pgp_writer_alloc that allocates a
given amount of space upfront. If the amount of data written to
the writer can be known or approximated beforehand, this will
avoid any potentially costly reallocations.
-rw-r--r-- | openpgp-ffi/include/sequoia/io.h | 14 | ||||
-rw-r--r-- | openpgp-ffi/src/io.rs | 68 |
2 files changed, 80 insertions, 2 deletions
diff --git a/openpgp-ffi/include/sequoia/io.h b/openpgp-ffi/include/sequoia/io.h index 35c397dc..6fcc7e87 100644 --- a/openpgp-ffi/include/sequoia/io.h +++ b/openpgp-ffi/include/sequoia/io.h @@ -109,6 +109,20 @@ pgp_writer_t pgp_writer_from_bytes (uint8_t *buf, size_t len); pgp_writer_t pgp_writer_alloc (void **buf, size_t *len); /*/ +/// Creates an allocating writer reserving space upfront. +/// +/// Variant of [`pgp_writer_alloc`] that makes sure that the buffer +/// can be filled up to `capacity` without causing a reallocation by +/// allocating space upfront. +/// +/// # Errors +/// +/// Returns `NULL` if the initial allocation failed. +/*/ +pgp_writer_t pgp_writer_alloc_with_capacity (void **buf, size_t *len, + size_t capacity); + +/*/ /// The callback type for the generic callback-based writer interface. /*/ typedef ssize_t (*pgp_writer_cb_t) (void *cookie, const void *buf, size_t len); diff --git a/openpgp-ffi/src/io.rs b/openpgp-ffi/src/io.rs index 8be56697..d255e7ed 100644 --- a/openpgp-ffi/src/io.rs +++ b/openpgp-ffi/src/io.rs @@ -261,6 +261,9 @@ fn pgp_writer_from_bytes(buf: *mut u8, len: size_t) -> *mut Writer { /// The caller is responsible to `free` it once the writer has been /// destroyed. /// +/// If you know the size of the data written to it, or can approximate +/// it, consider using [`pgp_writer_alloc_with_capacity`]. +/// /// # Sending objects across thread boundaries /// /// If you send a Sequoia object (like a pgp_writer_stack_t) that @@ -320,11 +323,72 @@ fn pgp_writer_from_bytes(buf: *mut u8, len: size_t) -> *mut Writer { #[::sequoia_ffi_macros::extern_fn] #[no_mangle] pub extern "C" fn pgp_writer_alloc(buf: *mut *mut c_void, len: *mut size_t) -> *mut Writer { + let len_ = ffi_param_ref_mut!(len); + + // Assume that the capacity is the current length. + let capacity = *len_; + + pgp_writer_alloc_with_capacity(buf, len, capacity) +} + +/// Creates an allocating writer reserving space upfront. +/// +/// Variant of [`pgp_writer_alloc`] that makes sure that the buffer +/// can be filled up to `capacity` without causing a reallocation by +/// allocating space upfront. +/// +/// # Errors +/// +/// Returns `NULL` if the initial allocation failed. +/// +/// # Examples +/// +/// ```c +/// #include <assert.h> +/// #include <stdlib.h> +/// #include <string.h> +/// +/// #include <sequoia/openpgp.h> +/// +/// /* Prepare a buffer. */ +/// void *buf = NULL; +/// size_t len = 0; +/// +/// /* The allocating writer reallocates the buffer so that it grows +/// if more data is written to it. However, if we allocate enough +/// space upfront, we can completely avoid the possibly costly +/// reallocations. Demonstrate that. */ +/// pgp_writer_t sink = pgp_writer_alloc_with_capacity (&buf, &len, 29 * 4096); +/// void *initial_allocation = buf; +/// for (int i = 0; i < 4096; i++) +/// pgp_writer_write (NULL, sink, +/// (uint8_t *) "This is a string of length 29", 29); +/// pgp_writer_free (sink); +/// assert (len == 29 * 4096); +/// assert (memcmp (buf, "This is a string of length 29This", 29 + 4) == 0); +/// assert (buf == initial_allocation); +/// free (buf); +/// ``` +#[::sequoia_ffi_macros::extern_fn] #[no_mangle] pub extern "C" +fn pgp_writer_alloc_with_capacity(buf: *mut *mut c_void, len: *mut size_t, + capacity: size_t) + -> *mut Writer { let buf = ffi_param_ref_mut!(buf); let len = ffi_param_ref_mut!(len); - // Assume that the capacity is the current length. - let capacity = *len; + // Sanitize capacity. + let capacity = capacity.max(*len); + + if capacity > *len { + let new = unsafe { + realloc(*buf, capacity) + }; + if new.is_null() { + return std::ptr::null_mut(); + } + + *buf = new; + } let w = WriterKind::Generic(Box::new(WriterAlloc(Mutex::new(Buffer { buf: buf, |