libdivecomputer/src/rbstream.c
Jef Driesen e0cf41a14e Add some extra parameter validation
The ringbuffer boundary addresses (begin/end) should be ordered
correctly, and the packet size should be smaller than the ringbuffer
size, otherwise the code won't work as expected.
2024-03-14 12:24:45 +01:00

189 lines
4.9 KiB
C

/*
* libdivecomputer
*
* Copyright (C) 2016 Jef Driesen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <stdlib.h>
#include <string.h>
#include "rbstream.h"
#include "context-private.h"
#include "device-private.h"
struct dc_rbstream_t {
dc_device_t *device;
unsigned int pagesize;
unsigned int packetsize;
unsigned int begin;
unsigned int end;
unsigned int address;
unsigned int available;
unsigned int skip;
unsigned char cache[];
};
static unsigned int
ifloor (unsigned int x, unsigned int n)
{
// Round down to next lower multiple.
return (x / n) * n;
}
static unsigned int
iceil (unsigned int x, unsigned int n)
{
// Round up to next higher multiple.
return ((x + n - 1) / n) * n;
}
dc_status_t
dc_rbstream_new (dc_rbstream_t **out, dc_device_t *device, unsigned int pagesize, unsigned int packetsize, unsigned int begin, unsigned int end, unsigned int address)
{
dc_rbstream_t *rbstream = NULL;
if (out == NULL || device == NULL)
return DC_STATUS_INVALIDARGS;
// Page and packet size should be non-zero.
if (pagesize == 0 || packetsize == 0) {
ERROR (device->context, "Zero length page or packet size!");
return DC_STATUS_INVALIDARGS;
}
// Packet size should be a multiple of the page size.
if (packetsize % pagesize != 0) {
ERROR (device->context, "Packet size not a multiple of the page size!");
return DC_STATUS_INVALIDARGS;
}
// Ringbuffer boundaries should be aligned to the page size.
if (begin % pagesize != 0 || end % pagesize != 0) {
ERROR (device->context, "Ringbuffer not aligned to the page size!");
return DC_STATUS_INVALIDARGS;
}
// Ringbuffer boundaries should not be reversed.
if (begin > end) {
ERROR (device->context, "Ringbuffer boundaries reversed!");
return DC_STATUS_INVALIDARGS;
}
// Packet size should be smaller than the ringbuffer size.
if (packetsize > (end - begin)) {
ERROR (device->context, "Packet size larger than the ringbuffer size!");
return DC_STATUS_INVALIDARGS;
}
// Address should be inside the ringbuffer.
if (address < begin || address > end) {
ERROR (device->context, "Address outside the ringbuffer!");
return DC_STATUS_INVALIDARGS;
}
// Allocate memory.
rbstream = (dc_rbstream_t *) malloc (sizeof(*rbstream) + packetsize);
if (rbstream == NULL) {
ERROR (device->context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
rbstream->device = device;
rbstream->pagesize = pagesize;
rbstream->packetsize = packetsize;
rbstream->begin = begin;
rbstream->end = end;
rbstream->address = iceil(address, pagesize);
rbstream->available = 0;
rbstream->skip = rbstream->address - address;
*out = rbstream;
return DC_STATUS_SUCCESS;
}
dc_status_t
dc_rbstream_read (dc_rbstream_t *rbstream, dc_event_progress_t *progress, unsigned char data[], unsigned int size)
{
dc_status_t rc = DC_STATUS_SUCCESS;
if (rbstream == NULL)
return DC_STATUS_INVALIDARGS;
unsigned int address = rbstream->address;
unsigned int available = rbstream->available;
unsigned int skip = rbstream->skip;
unsigned int nbytes = 0;
unsigned int offset = size;
while (nbytes < size) {
if (available == 0) {
// Handle the ringbuffer wrap point.
if (address == rbstream->begin)
address = rbstream->end;
// Calculate the packet size.
unsigned int len = rbstream->packetsize;
if (rbstream->begin + len > address)
len = address - rbstream->begin;
// Move to the begin of the current packet.
address -= len;
// Read the packet into the cache.
rc = dc_device_read (rbstream->device, address, rbstream->cache, rbstream->packetsize);
if (rc != DC_STATUS_SUCCESS)
return rc;
available = len - skip;
skip = 0;
}
unsigned int length = available;
if (nbytes + length > size)
length = size - nbytes;
offset -= length;
available -= length;
memcpy (data + offset, rbstream->cache + available, length);
// Update and emit a progress event.
if (progress) {
progress->current += length;
device_event_emit (rbstream->device, DC_EVENT_PROGRESS, progress);
}
nbytes += length;
}
rbstream->address = address;
rbstream->available = available;
rbstream->skip = skip;
return rc;
}
dc_status_t
dc_rbstream_free (dc_rbstream_t *rbstream)
{
free (rbstream);
return DC_STATUS_SUCCESS;
}