/*
* Copyright (c) 2018-2019 Richard Braun.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Upstream site with license notes :
* http://git.sceen.net/rbraun/librbraun.git/
*/
#include
#include
#include
#include
#include
#include
#include
#include
/*
* Message header.
*
* The size denotes the size of the data, without the header.
*/
union mbuf_hdr {
uint8_t size1;
uint16_t size2;
uint32_t size4;
#ifdef __LP64__
uint64_t size8;
#endif /* __LP64__ */
};
static size_t
mbuf_compute_hdr_size(unsigned int order)
{
return order / CHAR_BIT;
}
static void *
mbuf_hdr_get_msg_size_addr(union mbuf_hdr *hdr, unsigned int order)
{
switch (order) {
case 8:
return &hdr->size1;
case 16:
return &hdr->size2;
default:
#ifdef __LP64__
return &hdr->size8;
case 32:
#endif /* __LP64__ */
return &hdr->size4;
}
}
static size_t
mbuf_hdr_get_msg_size(const union mbuf_hdr *hdr, unsigned int order)
{
switch (order) {
case 8:
return hdr->size1;
case 16:
return hdr->size2;
default:
#ifdef __LP64__
return hdr->size8;
case 32:
#endif /* __LP64__ */
return hdr->size4;
}
}
static void
mbuf_hdr_init(union mbuf_hdr *hdr, unsigned int order, size_t size)
{
switch (order) {
case 8:
assert(size <= UINT8_MAX);
hdr->size1 = size;
break;
case 16:
assert(size <= UINT16_MAX);
hdr->size2 = size;
break;
default:
#ifdef __LP64__
hdr->size8 = size;
break;
case 32:
assert(size <= UINT32_MAX);
#endif /* __LP64__ */
hdr->size4 = size;
}
}
static size_t
mbuf_hdr_get_total_msg_size(const union mbuf_hdr *hdr, unsigned int order)
{
return mbuf_compute_hdr_size(order) + mbuf_hdr_get_msg_size(hdr, order);
}
static unsigned int
mbuf_compute_order(size_t max_msg_size)
{
unsigned int order;
assert(max_msg_size != 0);
order = (sizeof(max_msg_size) * CHAR_BIT) - __builtin_clzl(max_msg_size);
return P2ROUND(order, CHAR_BIT);
}
void
mbuf_init(struct mbuf *mbuf, void *buf, size_t capacity, size_t max_msg_size)
{
cbuf_init(&mbuf->cbuf, buf, capacity);
mbuf->max_msg_size = max_msg_size;
mbuf->order = mbuf_compute_order(max_msg_size);
}
void
mbuf_clear(struct mbuf *mbuf)
{
return cbuf_clear(&mbuf->cbuf);
}
static void
mbuf_clear_old_msgs(struct mbuf *mbuf, size_t total_size)
{
union mbuf_hdr hdr;
void *msg_size_addr;
size_t hdr_size;
hdr_size = mbuf_compute_hdr_size(mbuf->order);
msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order);
do {
size_t msg_size, size;
int error;
size = hdr_size;
error = cbuf_pop(&mbuf->cbuf, msg_size_addr, &size);
assert(!error);
if (size == 0) {
break;
}
msg_size = mbuf_hdr_get_msg_size(&hdr, mbuf->order);
size = msg_size;
error = cbuf_pop(&mbuf->cbuf, NULL, &size);
assert(!error && (size == msg_size));
} while (cbuf_avail_size(&mbuf->cbuf) < total_size);
}
int
mbuf_push(struct mbuf *mbuf, const void *buf, size_t size, bool erase)
{
union mbuf_hdr hdr;
void *msg_size_addr;
size_t hdr_size, total_size;
int error;
if (size > mbuf->max_msg_size) {
return EINVAL;
}
mbuf_hdr_init(&hdr, mbuf->order, size);
total_size = mbuf_hdr_get_total_msg_size(&hdr, mbuf->order);
if (total_size > cbuf_avail_size(&mbuf->cbuf)) {
if (!erase || (total_size > cbuf_capacity(&mbuf->cbuf))) {
return EMSGSIZE;
}
mbuf_clear_old_msgs(mbuf, total_size);
}
hdr_size = mbuf_compute_hdr_size(mbuf->order);
msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order);
error = cbuf_push(&mbuf->cbuf, msg_size_addr, hdr_size, erase);
assert(!error);
error = cbuf_push(&mbuf->cbuf, buf, size, erase);
assert(!error);
return 0;
}
int
mbuf_pop(struct mbuf *mbuf, void *buf, size_t *sizep)
{
size_t start;
int error;
start = cbuf_start(&mbuf->cbuf);
error = mbuf_read(mbuf, &start, buf, sizep);
if (!error) {
cbuf_set_start(&mbuf->cbuf, start);
}
return error;
}
int
mbuf_read(const struct mbuf *mbuf, size_t *indexp, void *buf, size_t *sizep)
{
union mbuf_hdr hdr;
void *msg_size_addr;
size_t hdr_size, msg_size, size;
int error;
hdr_size = mbuf_compute_hdr_size(mbuf->order);
msg_size_addr = mbuf_hdr_get_msg_size_addr(&hdr, mbuf->order);
size = hdr_size;
error = cbuf_read(&mbuf->cbuf, *indexp, msg_size_addr, &size);
if (error) {
return error;
} else if (size == 0) {
return EAGAIN;
}
assert(size == hdr_size);
msg_size = mbuf_hdr_get_msg_size(&hdr, mbuf->order);
if (msg_size > *sizep) {
error = EMSGSIZE;
goto out;
}
size = msg_size;
error = cbuf_read(&mbuf->cbuf, *indexp + hdr_size, buf, &size);
if (error) {
goto out;
}
assert(size == msg_size);
*indexp += hdr_size + size;
out:
*sizep = msg_size;
return error;
}