eina_binbuf: allow expand & usage of extra bytes.

Some code needs to read directly into eina_binbuf to avoid an extra
copy from eina_binbuf_append* variants.

This can be achieved by using eina_binbuf_expand(), which returns a
write-able slice of the spare bytes. Once they are used,
eina_binbuf_use() should be called to increment "buf->len", which is
used by all other binbuf functions such as eina_binbuf_length_get() or
eina_binbuf_append_slice().
This commit is contained in:
Gustavo Sverzut Barbieri 2016-12-19 18:35:38 -02:00
parent 3680ae0bab
commit 09825cbe5d
5 changed files with 231 additions and 0 deletions

View File

@ -129,6 +129,58 @@ EAPI void eina_binbuf_free(Eina_Binbuf *buf) EINA_ARG_NONNULL(1);
*/
EAPI void eina_binbuf_reset(Eina_Binbuf *buf) EINA_ARG_NONNULL(1);
/**
* @brief Expand a buffer, making room for at least @a minimum_unused_space.
*
* One of the properties of the buffer is that it may overallocate
* space, thus it may have more than eina_binbuf_length_get() bytes
* allocated. How much depends on buffer growing logic, but this
* function allows one to request a minimum amount of bytes to be
* allocated at the end of the buffer.
*
* This is particularly useful to write directly to buffer's memory
* (ie: a call to read(2)). After the bytes are used call
* eina_binbuf_use() to mark them as such, so eina_binbuf_length_get()
* will consider the new bytes.
*
* @param buf The Buffer to expand.
* @param minimum_unused_space The minimum unused allocated space, in
* bytes, at the end of the buffer. Zero can be used to query
* the available slice of unused bytes.
*
* @return The slice of unused bytes. The slice length may be zero if
* @a minimum_unused_space couldn't be allocated, otherwise it
* will be at least @a minimum_unused_space. After bytes are used,
* mark them as such using eina_binbuf_use().
*
* @see eina_binbuf_rw_slice_get()
* @see eina_binbuf_use()
*
* @since 1.19
*/
EAPI Eina_Rw_Slice eina_binbuf_expand(Eina_Binbuf *buf, size_t minimum_unused_space) EINA_ARG_NONNULL(1);
/**
* @brief Mark more bytes as used.
*
* This function should be used after eina_binbuf_expand(), marking
* the extra bytes returned there as used, then they will be
* considered in all other functions, such as eina_binbuf_length_get().
*
* @param buf The buffer to mark extra bytes as used.
* @param extra_bytes the number of bytes to be considered used, must
* be between zero and the length of the slice returned by
* eina_binbuf_expand().
*
* @return #EINA_TRUE on success, #EINA_FALSE on failure, such as @a
* extra_bytes is too big or @a buf is NULL.
*
* @see eina_binbuf_expand()
*
* @since 1.19
*/
EAPI Eina_Bool eina_binbuf_use(Eina_Binbuf *buf, size_t extra_bytes) EINA_ARG_NONNULL(1);
/**
* @brief Append a string of exact length to a buffer, reallocating as necessary.
*
@ -330,6 +382,10 @@ EAPI Eina_Slice eina_binbuf_slice_get(const Eina_Binbuf *buf) EINA_WARN_UNUSED_R
* @return a read-write slice for the current contents. It may become
* invalid as soon as the @a buf is changed with calls such as
* eina_binbuf_append(), eina_binbuf_remove()
*
* @see eina_binbuf_expand()
*
* @since 1.19
*/
EAPI Eina_Rw_Slice eina_binbuf_rw_slice_get(const Eina_Binbuf *buf) EINA_WARN_UNUSED_RESULT EINA_ARG_NONNULL(1);

View File

@ -100,6 +100,21 @@ _FUNC_EXPAND(reset)(_STRBUF_STRUCT_NAME *buf)
eina_strbuf_common_reset(_STRBUF_CSIZE, buf);
}
EAPI Eina_Rw_Slice
_FUNC_EXPAND(expand)(_STRBUF_STRUCT_NAME *buf, size_t minimum_unused_space)
{
Eina_Rw_Slice ret = {.len = 0, .mem = NULL};
EINA_MAGIC_CHECK_STRBUF(buf, ret);
return eina_strbuf_common_expand(_STRBUF_CSIZE, buf, minimum_unused_space);
}
EAPI Eina_Bool
_FUNC_EXPAND(use)(_STRBUF_STRUCT_NAME *buf, size_t extra_bytes)
{
EINA_MAGIC_CHECK_STRBUF(buf, EINA_FALSE);
return eina_strbuf_common_use(buf, extra_bytes);
}
EAPI Eina_Bool
_FUNC_EXPAND(append_length)(_STRBUF_STRUCT_NAME *buf, const _STRBUF_DATA_TYPE *str, size_t length)
{

View File

@ -378,6 +378,88 @@ eina_strbuf_common_reset(size_t csize, Eina_Strbuf *buf)
memset(buf->buf, 0, csize);
}
/**
* @internal
* @brief Expand a buffer, making room for at least @a minimum_unused_space.
*
* One of the properties of the buffer is that it may overallocate
* space, thus it may have more than eina_strbuf_common_length_get() bytes
* allocated. How much depends on buffer growing logic, but this
* function allows one to request a minimum amount of bytes to be
* allocated at the end of the buffer.
*
* This is particularly useful to write directly to buffer's memory
* (ie: a call to read(2)). After the bytes are used call
* eina_strbuf_common_use() to mark them as such, so
* eina_strbuf_common_length_get() will consider the new bytes.
*
* @param csize the character size
* @param buf The Buffer to expand.
* @param minimum_unused_space The minimum unused allocated space, in
* bytes, at the end of the buffer. Zero can be used to query
* the available slice of unused bytes.
*
* @return The slice of unused bytes. The slice length may be zero if
* @a minimum_unused_space couldn't be allocated, otherwise it
* will be at least @a minimum_unused_space. After bytes are used,
* mark them as such using eina_strbuf_common_use().
*
* @see eina_strbuf_common_rw_slice_get()
* @see eina_strbuf_common_use()
*
* @since 1.19
*/
Eina_Rw_Slice
eina_strbuf_common_expand(size_t csize,
Eina_Strbuf *buf,
size_t minimum_unused_space)
{
Eina_Rw_Slice ret = { .mem = NULL, .len = 0 };
if (EINA_LIKELY(buf->len + minimum_unused_space < buf->size)) goto end;
if (EINA_UNLIKELY(!_eina_strbuf_common_grow(csize, buf, buf->len + minimum_unused_space)))
return ret;
end:
ret.mem = (unsigned char *)buf->buf + (buf->len * csize);
ret.len = buf->size - buf->len - 1;
return ret;
}
/**
* @internal
* @brief Mark more bytes as used.
*
* This function should be used after eina_strbuf_common_expand(),
* marking the extra bytes returned there as used, then they will be
* considered in all other functions, such as
* eina_strbuf_common_length_get().
*
* @param csize the character size
* @param buf The buffer to mark extra bytes as used.
* @param extra_bytes the number of bytes to be considered used, must
* be between zero and the length of the slice returned by
* eina_strbuf_common_expand().
*
* @return #EINA_TRUE on success, #EINA_FALSE on failure, such as @a
* extra_bytes is too big or @a buf is NULL.
*
* @see eina_strbuf_common_expand()
*
* @since 1.19
*/
Eina_Bool
eina_strbuf_common_use(Eina_Strbuf *buf,
size_t extra_bytes)
{
if (EINA_UNLIKELY(buf->size < buf->len + extra_bytes + 1))
return EINA_FALSE;
buf->len += extra_bytes;
return EINA_TRUE;
}
/**
* @internal
* @brief Append a string to a buffer, reallocating as necessary.

View File

@ -52,6 +52,15 @@ void
eina_strbuf_common_free(Eina_Strbuf *buf);
void
eina_strbuf_common_reset(size_t csize, Eina_Strbuf *buf);
Eina_Rw_Slice
eina_strbuf_common_expand(size_t csize,
Eina_Strbuf *buf,
size_t minimum_unused_space);
Eina_Bool
eina_strbuf_common_use(Eina_Strbuf *buf,
size_t extra_bytes);
Eina_Bool
eina_strbuf_common_append(size_t csize,
Eina_Strbuf *buf,

View File

@ -306,6 +306,74 @@ START_TEST(binbuf_realloc)
}
END_TEST
START_TEST(binbuf_expand)
{
Eina_Binbuf *buf;
Eina_Rw_Slice rw_slice;
Eina_Slice ro_slice;
Eina_Slice hello_world = EINA_SLICE_STR_LITERAL("Hello World");
Eina_Slice hi_there = EINA_SLICE_STR_LITERAL("Hi There");
size_t i;
Eina_Bool r;
eina_init();
buf = eina_binbuf_new();
fail_if(!buf);
rw_slice = eina_binbuf_rw_slice_get(buf);
ck_assert_int_eq(rw_slice.len, 0);
/* force it to grow to 'Hello World' */
r = eina_binbuf_append_slice(buf, hello_world);
ck_assert_int_eq(r, EINA_TRUE);
ro_slice = eina_binbuf_slice_get(buf);
ck_assert_int_eq(ro_slice.len, hello_world.len);
ck_assert_int_eq(eina_slice_compare(ro_slice, hello_world), 0);
/* reset doesn't change allocated size, 'Hi there' will fit */
eina_binbuf_reset(buf);
rw_slice = eina_binbuf_expand(buf, hi_there.len);
ck_assert_int_ge(rw_slice.len, hi_there.len);
/* access bytes directly */
rw_slice = eina_rw_slice_copy(rw_slice, hi_there);
r = eina_binbuf_use(buf, rw_slice.len);
ck_assert_int_eq(r, EINA_TRUE);
ck_assert_int_eq(eina_slice_compare(eina_binbuf_slice_get(buf), hi_there), 0);
/* start with 'Hello World */
eina_binbuf_reset(buf);
r = eina_binbuf_append_slice(buf, hello_world);
ck_assert_int_eq(r, EINA_TRUE);
/* force it to realloc */
rw_slice = eina_binbuf_expand(buf, 8192);
ck_assert_int_ge(rw_slice.len, 8192);
ck_assert_ptr_ne(rw_slice.mem, NULL);
memset(rw_slice.mem, 0xfe, rw_slice.len);
r = eina_binbuf_use(buf, rw_slice.len);
ck_assert_int_eq(r, EINA_TRUE);
r = eina_binbuf_use(buf, 1); /* would go too much */
ck_assert_int_eq(r, EINA_FALSE);
ro_slice = eina_binbuf_slice_get(buf);
ck_assert_int_eq(ro_slice.len, hello_world.len + rw_slice.len);
ck_assert_int_eq(memcmp(ro_slice.mem, hello_world.mem, hello_world.len), 0);
for (i = hello_world.len; i < ro_slice.len; i++)
ck_assert_int_eq(ro_slice.bytes[i], 0xfe);
eina_binbuf_free(buf);
eina_shutdown();
}
END_TEST
void
eina_test_binbuf(TCase *tc)
{
@ -315,4 +383,5 @@ eina_test_binbuf(TCase *tc)
tcase_add_test(tc, binbuf_realloc);
tcase_add_test(tc, binbuf_manage_simple);
tcase_add_test(tc, binbuf_manage_read_only_simple);
tcase_add_test(tc, binbuf_expand);
}