* Copyright 2006-2016, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Axel Dörfler, axeld@pinc-software.de
* Ingo Weinhold, ingo_weinhold@gmx.de
*/
#include "utility.h"
#include <net_buffer.h>
#include <slab/Slab.h>
#include <tracing.h>
#include <util/list.h>
#include <ByteOrder.h>
#include <debug.h>
#include <kernel.h>
#include <KernelExport.h>
#include <util/DoublyLinkedList.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/uio.h>
#include "ancillary_data.h"
#include "interfaces.h"
#include "paranoia_config.h"
#ifdef TRACE_BUFFER
# define TRACE(x) dprintf x
#else
# define TRACE(x) ;
#endif
#define BUFFER_SIZE 2048
#define ENABLE_DEBUGGER_COMMANDS 1
#define ENABLE_STATS 1
#define PARANOID_BUFFER_CHECK NET_BUFFER_PARANOIA
#define COMPONENT_PARANOIA_LEVEL NET_BUFFER_PARANOIA
#include <debug_paranoia.h>
#define DATA_NODE_READ_ONLY 0x1
#define DATA_NODE_STORED_HEADER 0x2
struct header_space {
uint16 size;
uint16 free;
};
struct free_data {
struct free_data* next;
uint16 size;
};
struct data_header {
int32 ref_count;
addr_t physical_address;
free_data* first_free;
uint8* data_end;
header_space space;
uint16 tail_space;
};
struct data_node {
struct list_link link;
struct data_header* header;
struct data_header* located;
size_t offset;
uint8* start;
uint16 flags;
uint16 used;
uint16 HeaderSpace() const
{
if ((flags & DATA_NODE_READ_ONLY) != 0)
return 0;
return header->space.free;
}
void AddHeaderSpace(uint16 toAdd)
{
if ((flags & DATA_NODE_READ_ONLY) == 0) {
header->space.size += toAdd;
header->space.free += toAdd;
}
}
void SubtractHeaderSpace(uint16 toSubtract)
{
if ((flags & DATA_NODE_READ_ONLY) == 0) {
header->space.size -= toSubtract;
header->space.free -= toSubtract;
}
}
uint16 TailSpace() const
{
if ((flags & DATA_NODE_READ_ONLY) != 0)
return 0;
return header->tail_space;
}
void SetTailSpace(uint16 space)
{
if ((flags & DATA_NODE_READ_ONLY) == 0)
header->tail_space = space;
}
void FreeSpace()
{
if ((flags & DATA_NODE_READ_ONLY) == 0) {
uint16 space = used + header->tail_space;
header->space.size += space;
header->space.free += space;
header->tail_space = 0;
}
}
};
struct net_buffer_private : net_buffer {
struct list buffers;
data_header* allocation_header;
ancillary_data_container* ancillary_data;
size_t stored_header_length;
struct {
struct sockaddr_storage source;
struct sockaddr_storage destination;
} storage;
};
#define DATA_HEADER_SIZE _ALIGN(sizeof(data_header))
#define DATA_NODE_SIZE _ALIGN(sizeof(data_node))
#define MAX_FREE_BUFFER_SIZE (BUFFER_SIZE - DATA_HEADER_SIZE)
static object_cache* sNetBufferCache;
static object_cache* sDataNodeCache;
static status_t append_data(net_buffer* buffer, const void* data, size_t size);
static status_t trim_data(net_buffer* _buffer, size_t newSize);
static status_t remove_header(net_buffer* _buffer, size_t bytes);
static status_t remove_trailer(net_buffer* _buffer, size_t bytes);
static status_t append_cloned_data(net_buffer* _buffer, net_buffer* _source,
uint32 offset, size_t bytes);
static status_t read_data(net_buffer* _buffer, size_t offset, void* data,
size_t size);
#if ENABLE_STATS
static int32 sAllocatedDataHeaderCount = 0;
static int32 sAllocatedNetBufferCount = 0;
static int32 sEverAllocatedDataHeaderCount = 0;
static int32 sEverAllocatedNetBufferCount = 0;
static int32 sMaxAllocatedDataHeaderCount = 0;
static int32 sMaxAllocatedNetBufferCount = 0;
#endif
#if NET_BUFFER_TRACING
namespace NetBufferTracing {
class NetBufferTraceEntry : public AbstractTraceEntry {
public:
NetBufferTraceEntry(net_buffer* buffer)
:
fBuffer(buffer)
{
#if NET_BUFFER_TRACING_STACK_TRACE
fStackTrace = capture_tracing_stack_trace(
NET_BUFFER_TRACING_STACK_TRACE, 0, false);
#endif
}
#if NET_BUFFER_TRACING_STACK_TRACE
virtual void DumpStackTrace(TraceOutput& out)
{
out.PrintStackTrace(fStackTrace);
}
#endif
protected:
net_buffer* fBuffer;
#if NET_BUFFER_TRACING_STACK_TRACE
tracing_stack_trace* fStackTrace;
#endif
};
class Create : public NetBufferTraceEntry {
public:
Create(size_t headerSpace, net_buffer* buffer)
:
NetBufferTraceEntry(buffer),
fHeaderSpace(headerSpace)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer create: header space: %lu -> buffer: %p",
fHeaderSpace, fBuffer);
}
private:
size_t fHeaderSpace;
};
class Free : public NetBufferTraceEntry {
public:
Free(net_buffer* buffer)
:
NetBufferTraceEntry(buffer)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer free: buffer: %p", fBuffer);
}
};
class Duplicate : public NetBufferTraceEntry {
public:
Duplicate(net_buffer* buffer, net_buffer* clone)
:
NetBufferTraceEntry(buffer),
fClone(clone)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer dup: buffer: %p -> %p", fBuffer, fClone);
}
private:
net_buffer* fClone;
};
class Clone : public NetBufferTraceEntry {
public:
Clone(net_buffer* buffer, bool shareFreeSpace, net_buffer* clone)
:
NetBufferTraceEntry(buffer),
fClone(clone),
fShareFreeSpace(shareFreeSpace)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer clone: buffer: %p, share free space: %s "
"-> %p", fBuffer, fShareFreeSpace ? "true" : "false", fClone);
}
private:
net_buffer* fClone;
bool fShareFreeSpace;
};
class Split : public NetBufferTraceEntry {
public:
Split(net_buffer* buffer, uint32 offset, net_buffer* newBuffer)
:
NetBufferTraceEntry(buffer),
fNewBuffer(newBuffer),
fOffset(offset)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer split: buffer: %p, offset: %lu "
"-> %p", fBuffer, fOffset, fNewBuffer);
}
private:
net_buffer* fNewBuffer;
uint32 fOffset;
};
class Merge : public NetBufferTraceEntry {
public:
Merge(net_buffer* buffer, net_buffer* otherBuffer, bool after)
:
NetBufferTraceEntry(buffer),
fOtherBuffer(otherBuffer),
fAfter(after)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer merge: buffers: %p + %p, after: %s "
"-> %p", fBuffer, fOtherBuffer, fAfter ? "true" : "false",
fOtherBuffer);
}
private:
net_buffer* fOtherBuffer;
bool fAfter;
};
class AppendCloned : public NetBufferTraceEntry {
public:
AppendCloned(net_buffer* buffer, net_buffer* source, uint32 offset,
size_t size)
:
NetBufferTraceEntry(buffer),
fSource(source),
fOffset(offset),
fSize(size)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer append cloned: buffer: %p, from: %p, "
"offset: %lu, size: %lu", fBuffer, fSource, fOffset, fSize);
}
private:
net_buffer* fSource;
uint32 fOffset;
size_t fSize;
};
class PrependSize : public NetBufferTraceEntry {
public:
PrependSize(net_buffer* buffer, size_t size)
:
NetBufferTraceEntry(buffer),
fSize(size)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer prepend size: buffer: %p, size: %lu", fBuffer,
fSize);
}
private:
size_t fSize;
};
class AppendSize : public NetBufferTraceEntry {
public:
AppendSize(net_buffer* buffer, size_t size)
:
NetBufferTraceEntry(buffer),
fSize(size)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer append size: buffer: %p, size: %lu", fBuffer,
fSize);
}
private:
size_t fSize;
};
class RemoveHeader : public NetBufferTraceEntry {
public:
RemoveHeader(net_buffer* buffer, size_t size)
:
NetBufferTraceEntry(buffer),
fSize(size)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer remove header: buffer: %p, size: %lu",
fBuffer, fSize);
}
private:
size_t fSize;
};
class Trim : public NetBufferTraceEntry {
public:
Trim(net_buffer* buffer, size_t size)
:
NetBufferTraceEntry(buffer),
fSize(size)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer trim: buffer: %p, size: %lu",
fBuffer, fSize);
}
private:
size_t fSize;
};
class Read : public NetBufferTraceEntry {
public:
Read(net_buffer* buffer, uint32 offset, void* data, size_t size)
:
NetBufferTraceEntry(buffer),
fData(data),
fOffset(offset),
fSize(size)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer read: buffer: %p, offset: %lu, size: %lu, "
"data: %p", fBuffer, fOffset, fSize, fData);
}
private:
void* fData;
uint32 fOffset;
size_t fSize;
};
class Write : public NetBufferTraceEntry {
public:
Write(net_buffer* buffer, uint32 offset, const void* data, size_t size)
:
NetBufferTraceEntry(buffer),
fData(data),
fOffset(offset),
fSize(size)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer write: buffer: %p, offset: %lu, size: %lu, "
"data: %p", fBuffer, fOffset, fSize, fData);
}
private:
const void* fData;
uint32 fOffset;
size_t fSize;
};
#if NET_BUFFER_TRACING >= 2
class DataHeaderTraceEntry : public AbstractTraceEntry {
public:
DataHeaderTraceEntry(data_header* header)
:
fHeader(header)
{
}
protected:
data_header* fHeader;
};
class CreateDataHeader : public DataHeaderTraceEntry {
public:
CreateDataHeader(data_header* header)
:
DataHeaderTraceEntry(header)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer data header create: header: %p", fHeader);
}
};
class AcquireDataHeader : public DataHeaderTraceEntry {
public:
AcquireDataHeader(data_header* header, int32 refCount)
:
DataHeaderTraceEntry(header),
fRefCount(refCount)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer data header acquire: header: %p "
"-> ref count: %ld", fHeader, fRefCount);
}
private:
int32 fRefCount;
};
class ReleaseDataHeader : public DataHeaderTraceEntry {
public:
ReleaseDataHeader(data_header* header, int32 refCount)
:
DataHeaderTraceEntry(header),
fRefCount(refCount)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("net buffer data header release: header: %p "
"-> ref count: %ld", fHeader, fRefCount);
}
private:
int32 fRefCount;
};
# define T2(x) new(std::nothrow) NetBufferTracing::x
#else
# define T2(x)
#endif
}
# define T(x) new(std::nothrow) NetBufferTracing::x
#else
# define T(x)
# define T2(x)
#endif
static void
dump_address(const char* prefix, sockaddr* address,
net_interface_address* interfaceAddress)
{
if (address == NULL || address->sa_len == 0)
return;
if (interfaceAddress == NULL || interfaceAddress->domain == NULL) {
dprintf(" %s: length %u, family %u\n", prefix, address->sa_len,
address->sa_family);
dump_block((char*)address + 2, address->sa_len - 2, " ");
} else {
char buffer[64];
interfaceAddress->domain->address_module->print_address_buffer(address,
buffer, sizeof(buffer), true);
dprintf(" %s: %s\n", prefix, buffer);
}
}
static void
dump_buffer(net_buffer* _buffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
dprintf("buffer %p, size %" B_PRIu32 ", msg_flags %" B_PRIx32 ", buffer_flags %" B_PRIx16
", stored header %" B_PRIuSIZE ", interface address %p\n", buffer, buffer->size,
buffer->msg_flags, buffer->buffer_flags, buffer->stored_header_length,
buffer->interface_address);
dump_address("source", buffer->source, buffer->interface_address);
dump_address("destination", buffer->destination, buffer->interface_address);
data_node* node = NULL;
while ((node = (data_node*)list_get_next_item(&buffer->buffers, node))
!= NULL) {
dprintf(" node %p, offset %lu, used %u, header %u, tail %u, "
"header %p\n", node, node->offset, node->used, node->HeaderSpace(),
node->TailSpace(), node->header);
if ((node->flags & DATA_NODE_STORED_HEADER) != 0) {
dump_block((char*)node->start - buffer->stored_header_length,
min_c(buffer->stored_header_length, 64), " s ");
}
dump_block((char*)node->start, min_c(node->used, 64), " ");
}
}
#if ENABLE_DEBUGGER_COMMANDS
static int
dump_net_buffer(int argc, char** argv)
{
if (argc != 2) {
kprintf("usage: %s [address]\n", argv[0]);
return 0;
}
dump_buffer((net_buffer*)parse_expression(argv[1]));
return 0;
}
#endif
#if ENABLE_STATS
static int
dump_net_buffer_stats(int argc, char** argv)
{
kprintf("allocated data headers: %7" B_PRId32 " / %7" B_PRId32 ", peak %7"
B_PRId32 "\n", sAllocatedDataHeaderCount, sEverAllocatedDataHeaderCount,
sMaxAllocatedDataHeaderCount);
kprintf("allocated net buffers: %7" B_PRId32 " / %7" B_PRId32 ", peak %7"
B_PRId32 "\n", sAllocatedNetBufferCount, sEverAllocatedNetBufferCount,
sMaxAllocatedNetBufferCount);
return 0;
}
#endif
#if PARANOID_BUFFER_CHECK
static void
check_buffer(net_buffer* _buffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
size_t size = 0;
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
while (node != NULL) {
if (node->offset != size) {
panic("net_buffer %p: bad node %p offset (%lu vs. %lu)",
buffer, node, node->offset, size);
return;
}
size += node->used;
node = (data_node*)list_get_next_item(&buffer->buffers, node);
}
if (size != buffer->size) {
panic("net_buffer %p size != sum of its data node sizes (%lu vs. %lu)",
buffer, buffer->size, size);
return;
}
}
#if 0
static void
check_buffer_contents(net_buffer* buffer, size_t offset, const void* data,
size_t size)
{
void* bufferData = malloc(size);
if (bufferData == NULL)
return;
if (read_data(buffer, offset, bufferData, size) == B_OK) {
if (memcmp(bufferData, data, size) != 0) {
int32 index = 0;
while (((uint8*)data)[index] == ((uint8*)bufferData)[index])
index++;
panic("check_buffer_contents(): contents check failed at index "
"%ld, buffer: %p, offset: %lu, size: %lu", index, buffer,
offset, size);
}
} else {
panic("failed to read from buffer %p, offset: %lu, size: %lu",
buffer, offset, size);
}
free(bufferData);
}
static void
check_buffer_contents(net_buffer* buffer, size_t offset, net_buffer* source,
size_t sourceOffset, size_t size)
{
void* bufferData = malloc(size);
if (bufferData == NULL)
return;
if (read_data(source, sourceOffset, bufferData, size) == B_OK) {
check_buffer_contents(buffer, offset, bufferData, size);
} else {
panic("failed to read from source buffer %p, offset: %lu, size: %lu",
source, sourceOffset, size);
}
free(bufferData);
}
#endif
# define CHECK_BUFFER(buffer) check_buffer(buffer)
#else
# define CHECK_BUFFER(buffer) do {} while (false)
#endif
static inline data_header*
allocate_data_header()
{
#if ENABLE_STATS
int32 current = atomic_add(&sAllocatedDataHeaderCount, 1) + 1;
int32 max = atomic_get(&sMaxAllocatedDataHeaderCount);
if (current > max)
atomic_test_and_set(&sMaxAllocatedDataHeaderCount, current, max);
atomic_add(&sEverAllocatedDataHeaderCount, 1);
#endif
return (data_header*)object_cache_alloc(sDataNodeCache, 0);
}
static inline net_buffer_private*
allocate_net_buffer()
{
#if ENABLE_STATS
int32 current = atomic_add(&sAllocatedNetBufferCount, 1) + 1;
int32 max = atomic_get(&sMaxAllocatedNetBufferCount);
if (current > max)
atomic_test_and_set(&sMaxAllocatedNetBufferCount, current, max);
atomic_add(&sEverAllocatedNetBufferCount, 1);
#endif
return (net_buffer_private*)object_cache_alloc(sNetBufferCache, 0);
}
static inline void
free_data_header(data_header* header)
{
#if ENABLE_STATS
if (header != NULL)
atomic_add(&sAllocatedDataHeaderCount, -1);
#endif
object_cache_free(sDataNodeCache, header, 0);
}
static inline void
free_net_buffer(net_buffer_private* buffer)
{
#if ENABLE_STATS
if (buffer != NULL)
atomic_add(&sAllocatedNetBufferCount, -1);
#endif
object_cache_free(sNetBufferCache, buffer, 0);
}
static data_header*
create_data_header(size_t headerSpace)
{
data_header* header = allocate_data_header();
if (header == NULL)
return NULL;
header->ref_count = 1;
header->physical_address = 0;
header->space.size = headerSpace;
header->space.free = headerSpace;
header->data_end = (uint8*)header + DATA_HEADER_SIZE;
header->tail_space = (uint8*)header + BUFFER_SIZE - header->data_end
- headerSpace;
header->first_free = NULL;
TRACE(("%d: create new data header %p\n", find_thread(NULL), header));
T2(CreateDataHeader(header));
return header;
}
static void
release_data_header(data_header* header)
{
int32 refCount = atomic_add(&header->ref_count, -1);
T2(ReleaseDataHeader(header, refCount - 1));
if (refCount != 1)
return;
TRACE(("%d: free header %p\n", find_thread(NULL), header));
free_data_header(header);
}
inline void
acquire_data_header(data_header* header)
{
int32 refCount = atomic_add(&header->ref_count, 1);
(void)refCount;
T2(AcquireDataHeader(header, refCount + 1));
}
static void
free_data_header_space(data_header* header, uint8* data, size_t size)
{
if (size < sizeof(free_data))
size = sizeof(free_data);
free_data* freeData = (free_data*)data;
freeData->next = header->first_free;
freeData->size = size;
header->first_free = freeData;
}
*/
static uint8*
alloc_data_header_space(data_header* header, size_t size)
{
if (size < sizeof(free_data))
size = sizeof(free_data);
size = _ALIGN(size);
if (header->first_free != NULL && header->first_free->size >= size) {
uint8* data = (uint8*)header->first_free;
header->first_free = header->first_free->next;
return data;
}
if (header->space.free < size) {
free_data* freeData = header->first_free;
free_data* last = NULL;
while (freeData != NULL) {
if (last != NULL && freeData->size >= size) {
last->next = freeData->next;
return (uint8*)freeData;
}
last = freeData;
freeData = freeData->next;
}
return NULL;
}
uint8* data = header->data_end;
header->data_end += size;
header->space.free -= size;
return data;
}
static uint8*
alloc_data_header_space(net_buffer_private* buffer, size_t size,
data_header** _header = NULL)
{
uint8* allocated = alloc_data_header_space(buffer->allocation_header, size);
if (allocated == NULL) {
data_header* header = create_data_header(MAX_FREE_BUFFER_SIZE);
if (header == NULL)
return NULL;
release_data_header(buffer->allocation_header);
buffer->allocation_header = header;
allocated = alloc_data_header_space(buffer->allocation_header, size);
}
if (_header != NULL)
*_header = buffer->allocation_header;
return allocated;
}
static data_node*
add_first_data_node(data_header* header)
{
data_node* node = (data_node*)alloc_data_header_space(header,
sizeof(data_node));
if (node == NULL)
return NULL;
TRACE(("%d: add first data node %p to header %p\n", find_thread(NULL),
node, header));
acquire_data_header(header);
memset(node, 0, sizeof(struct data_node));
node->located = header;
node->header = header;
node->offset = 0;
node->start = header->data_end + header->space.free;
node->used = 0;
node->flags = 0;
return node;
}
static data_node*
add_data_node(net_buffer_private* buffer, data_header* header)
{
data_header* located;
data_node* node = (data_node*)alloc_data_header_space(buffer,
sizeof(data_node), &located);
if (node == NULL)
return NULL;
TRACE(("%d: add data node %p to header %p\n", find_thread(NULL), node,
header));
acquire_data_header(header);
if (located != header)
acquire_data_header(located);
memset(node, 0, sizeof(struct data_node));
node->located = located;
node->header = header;
node->flags = 0;
return node;
}
void
remove_data_node(data_node* node)
{
data_header* located = node->located;
TRACE(("%d: remove data node %p from header %p (located %p)\n",
find_thread(NULL), node, node->header, located));
node->FreeSpace();
if (located != node->header)
release_data_header(node->header);
if (located == NULL)
return;
free_data_header_space(located, (uint8*)node, sizeof(data_node));
release_data_header(located);
}
static inline data_node*
get_node_at_offset(net_buffer_private* buffer, size_t offset)
{
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
while (node != NULL && node->offset + node->used <= offset)
node = (data_node*)list_get_next_item(&buffer->buffers, node);
return node;
}
\a to net_buffer. The source buffer will remain unchanged.
*/
static status_t
append_data_from_buffer(net_buffer* to, const net_buffer* from, size_t size)
{
net_buffer_private* source = (net_buffer_private*)from;
net_buffer_private* dest = (net_buffer_private*)to;
if (size > from->size)
return B_BAD_VALUE;
if (size == 0)
return B_OK;
data_node* nodeTo = get_node_at_offset(source, size);
if (nodeTo == NULL)
return B_BAD_VALUE;
data_node* node = (data_node*)list_get_first_item(&source->buffers);
if (node == NULL) {
CHECK_BUFFER(source);
return B_ERROR;
}
while (node != nodeTo) {
if (append_data(dest, node->start, node->used) < B_OK) {
CHECK_BUFFER(dest);
return B_ERROR;
}
node = (data_node*)list_get_next_item(&source->buffers, node);
}
int32 diff = node->offset + node->used - size;
if (append_data(dest, node->start, node->used - diff) < B_OK) {
CHECK_BUFFER(dest);
return B_ERROR;
}
CHECK_BUFFER(dest);
return B_OK;
}
static void
copy_metadata(net_buffer* destination, const net_buffer* source)
{
memcpy(destination->source, source->source,
min_c(source->source->sa_len, sizeof(sockaddr_storage)));
memcpy(destination->destination, source->destination,
min_c(source->destination->sa_len, sizeof(sockaddr_storage)));
destination->msg_flags = source->msg_flags;
destination->buffer_flags = source->buffer_flags;
destination->interface_address = source->interface_address;
if (destination->interface_address != NULL)
((InterfaceAddress*)destination->interface_address)->AcquireReference();
destination->offset = source->offset;
destination->protocol = source->protocol;
destination->type = source->type;
}
static net_buffer*
create_buffer(size_t headerSpace)
{
net_buffer_private* buffer = allocate_net_buffer();
if (buffer == NULL)
return NULL;
TRACE(("%d: create buffer %p\n", find_thread(NULL), buffer));
headerSpace = _ALIGN(headerSpace);
if (headerSpace < DATA_NODE_SIZE)
headerSpace = DATA_NODE_SIZE;
else if (headerSpace > MAX_FREE_BUFFER_SIZE)
headerSpace = MAX_FREE_BUFFER_SIZE;
data_header* header = create_data_header(headerSpace);
if (header == NULL) {
free_net_buffer(buffer);
return NULL;
}
buffer->allocation_header = header;
data_node* node = add_first_data_node(header);
list_init(&buffer->buffers);
list_add_item(&buffer->buffers, node);
buffer->ancillary_data = NULL;
buffer->stored_header_length = 0;
buffer->source = (sockaddr*)&buffer->storage.source;
buffer->destination = (sockaddr*)&buffer->storage.destination;
buffer->storage.source.ss_len = 0;
buffer->storage.destination.ss_len = 0;
buffer->interface_address = NULL;
buffer->offset = 0;
buffer->msg_flags = 0;
buffer->buffer_flags = 0;
buffer->size = 0;
CHECK_BUFFER(buffer);
CREATE_PARANOIA_CHECK_SET(buffer, "net_buffer");
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
T(Create(headerSpace, buffer));
return buffer;
}
static void
free_buffer(net_buffer* _buffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
TRACE(("%d: free buffer %p\n", find_thread(NULL), buffer));
T(Free(buffer));
CHECK_BUFFER(buffer);
DELETE_PARANOIA_CHECK_SET(buffer);
while (data_node* node
= (data_node*)list_remove_head_item(&buffer->buffers)) {
remove_data_node(node);
}
delete_ancillary_data_container(buffer->ancillary_data);
release_data_header(buffer->allocation_header);
if (buffer->interface_address != NULL)
((InterfaceAddress*)buffer->interface_address)->ReleaseReference();
free_net_buffer(buffer);
}
storage; they are completely independent from each other.
*/
static net_buffer*
duplicate_buffer(net_buffer* _buffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
ParanoiaChecker _(buffer);
TRACE(("%d: duplicate_buffer(buffer %p)\n", find_thread(NULL), buffer));
net_buffer* duplicate = create_buffer(DATA_NODE_SIZE);
if (duplicate == NULL)
return NULL;
TRACE(("%d: duplicate: %p)\n", find_thread(NULL), duplicate));
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
while (node != NULL) {
if (append_data(duplicate, node->start, node->used) < B_OK) {
free_buffer(duplicate);
CHECK_BUFFER(buffer);
return NULL;
}
node = (data_node*)list_get_next_item(&buffer->buffers, node);
}
copy_metadata(duplicate, buffer);
ASSERT(duplicate->size == buffer->size);
CHECK_BUFFER(buffer);
CHECK_BUFFER(duplicate);
RUN_PARANOIA_CHECKS(duplicate);
T(Duplicate(buffer, duplicate));
return duplicate;
}
If that data changes, it will be changed in the clone as well.
If \a shareFreeSpace is \c true, the cloned buffer may claim the free
space in the original buffer as the original buffer can still do. If you
are using this, it's your responsibility that only one of the buffers
will do this.
*/
static net_buffer*
clone_buffer(net_buffer* _buffer, bool shareFreeSpace)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
net_buffer* clone = create_buffer(MAX_FREE_BUFFER_SIZE);
if (clone == NULL)
return NULL;
if (append_cloned_data(clone, buffer, 0, buffer->size) != B_OK) {
free_buffer(clone);
return NULL;
}
copy_metadata(clone, buffer);
ASSERT(clone->size == buffer->size);
return clone;
#if 0
ParanoiaChecker _(buffer);
TRACE(("%d: clone_buffer(buffer %p)\n", find_thread(NULL), buffer));
net_buffer_private* clone = allocate_net_buffer();
if (clone == NULL)
return NULL;
TRACE(("%d: clone: %p\n", find_thread(NULL), buffer));
data_node* sourceNode = (data_node*)list_get_first_item(&buffer->buffers);
if (sourceNode == NULL) {
free_net_buffer(clone);
return NULL;
}
clone->source = (sockaddr*)&clone->storage.source;
clone->destination = (sockaddr*)&clone->storage.destination;
list_init(&clone->buffers);
acquire_data_header(sourceNode->header);
data_node* node = &clone->first_node;
node->header = sourceNode->header;
node->located = NULL;
node->used_header_space = &node->own_header_space;
while (sourceNode != NULL) {
node->start = sourceNode->start;
node->used = sourceNode->used;
node->offset = sourceNode->offset;
if (shareFreeSpace) {
node->used_header_space = &sourceNode->header->space;
node->tail_space = sourceNode->tail_space;
} else {
node->used_header_space->size = 0;
node->used_header_space->free = 0;
node->tail_space = 0;
}
list_add_item(&clone->buffers, node);
sourceNode = (data_node*)list_get_next_item(&buffer->buffers,
sourceNode);
if (sourceNode == NULL)
break;
node = add_data_node(sourceNode->header);
if (node == NULL) {
panic("clone buffer hits size limit... (fix me)");
free_net_buffer(clone);
return NULL;
}
}
copy_metadata(clone, buffer);
ASSERT(clone->size == buffer->size);
CREATE_PARANOIA_CHECK_SET(clone, "net_buffer");
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, clone, &clone->size,
sizeof(clone->size));
CHECK_BUFFER(buffer);
CHECK_BUFFER(clone);
T(Clone(buffer, shareFreeSpace, clone));
return clone;
#endif
}
is returned as new buffer.
*/
static net_buffer*
split_buffer(net_buffer* from, uint32 offset)
{
net_buffer* buffer = create_buffer(DATA_NODE_SIZE);
if (buffer == NULL)
return NULL;
copy_metadata(buffer, from);
ParanoiaChecker _(from);
ParanoiaChecker _2(buffer);
TRACE(("%d: split_buffer(buffer %p -> %p, offset %" B_PRIu32 ")\n",
find_thread(NULL), from, buffer, offset));
if (append_data_from_buffer(buffer, from, offset) == B_OK) {
if (remove_header(from, offset) == B_OK) {
CHECK_BUFFER(from);
CHECK_BUFFER(buffer);
T(Split(from, offset, buffer));
return buffer;
}
}
free_buffer(buffer);
CHECK_BUFFER(from);
return NULL;
}
second buffer's contents will be appended to the first ones, else they
will be prepended.
The second buffer will be freed if this function succeeds.
*/
static status_t
merge_buffer(net_buffer* _buffer, net_buffer* _with, bool after)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
net_buffer_private* with = (net_buffer_private*)_with;
if (with == NULL)
return B_BAD_VALUE;
TRACE(("%d: merge buffer %p with %p (%s)\n", find_thread(NULL), buffer,
with, after ? "after" : "before"));
T(Merge(buffer, with, after));
ParanoiaChecker _(buffer);
CHECK_BUFFER(buffer);
CHECK_BUFFER(with);
data_node* before = NULL;
if (!after) {
data_node* node = NULL;
while (true) {
node = (data_node*)list_get_next_item(&buffer->buffers, node);
if (node == NULL)
break;
node->offset += with->size;
if (before == NULL)
before = node;
}
}
data_node* last = NULL;
while (true) {
data_node* node = (data_node*)list_get_next_item(&with->buffers, last);
if (node == NULL)
break;
if ((uint8*)node > (uint8*)node->header
&& (uint8*)node < (uint8*)node->header + BUFFER_SIZE) {
list_remove_item(&with->buffers, node);
with->size -= node->used;
} else {
data_node* newNode = add_data_node(buffer, node->header);
if (newNode == NULL) {
return ENOBUFS;
}
last = node;
*newNode = *node;
node = newNode;
}
if (after) {
list_add_item(&buffer->buffers, node);
node->offset = buffer->size;
} else
list_insert_item_before(&buffer->buffers, before, node);
buffer->size += node->used;
}
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
free_buffer(with);
CHECK_BUFFER(buffer);
return B_OK;
}
\return B_BAD_VALUE if you write outside of the buffers current
bounds.
*/
static status_t
write_data(net_buffer* _buffer, size_t offset, const void* data, size_t size)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
T(Write(buffer, offset, data, size));
ParanoiaChecker _(buffer);
if (offset + size > buffer->size)
return B_BAD_VALUE;
if (size == 0)
return B_OK;
data_node* node = get_node_at_offset(buffer, offset);
if (node == NULL)
return B_BAD_VALUE;
offset -= node->offset;
while (true) {
size_t written = min_c(size, node->used - offset);
if (IS_USER_ADDRESS(data)) {
if (user_memcpy(node->start + offset, data, written) != B_OK)
return B_BAD_ADDRESS;
} else
memcpy(node->start + offset, data, written);
size -= written;
if (size == 0)
break;
offset = 0;
data = (void*)((uint8*)data + written);
node = (data_node*)list_get_next_item(&buffer->buffers, node);
if (node == NULL)
return B_BAD_VALUE;
}
CHECK_BUFFER(buffer);
return B_OK;
}
static status_t
read_data(net_buffer* _buffer, size_t offset, void* data, size_t size)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
T(Read(buffer, offset, data, size));
ParanoiaChecker _(buffer);
if (offset + size > buffer->size)
return B_BAD_VALUE;
if (size == 0)
return B_OK;
data_node* node = get_node_at_offset(buffer, offset);
if (node == NULL)
return B_BAD_VALUE;
offset -= node->offset;
while (true) {
size_t bytesRead = min_c(size, node->used - offset);
if (IS_USER_ADDRESS(data)) {
if (user_memcpy(data, node->start + offset, bytesRead) != B_OK)
return B_BAD_ADDRESS;
} else
memcpy(data, node->start + offset, bytesRead);
size -= bytesRead;
if (size == 0)
break;
offset = 0;
data = (void*)((uint8*)data + bytesRead);
node = (data_node*)list_get_next_item(&buffer->buffers, node);
if (node == NULL)
return B_BAD_VALUE;
}
CHECK_BUFFER(buffer);
return B_OK;
}
static status_t
prepend_size(net_buffer* _buffer, size_t size, void** _contiguousBuffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
if (node == NULL) {
node = add_first_data_node(buffer->allocation_header);
if (node == NULL)
return B_NO_MEMORY;
}
T(PrependSize(buffer, size));
ParanoiaChecker _(buffer);
TRACE(("%d: prepend_size(buffer %p, size %ld) [has %u]\n",
find_thread(NULL), buffer, size, node->HeaderSpace()));
if ((node->flags & DATA_NODE_STORED_HEADER) != 0) {
node->AddHeaderSpace(buffer->stored_header_length);
node->flags &= ~DATA_NODE_STORED_HEADER;
buffer->stored_header_length = 0;
}
if (node->HeaderSpace() < size) {
size_t bytesLeft = size;
size_t sizePrepended = 0;
do {
if (node->HeaderSpace() == 0) {
size_t headerSpace = MAX_FREE_BUFFER_SIZE;
data_header* header = create_data_header(headerSpace);
if (header == NULL) {
remove_header(buffer, sizePrepended);
return B_NO_MEMORY;
}
data_node* previous = node;
node = (data_node*)add_first_data_node(header);
list_insert_item_before(&buffer->buffers, previous, node);
release_data_header(header);
}
size_t willConsume = min_c(bytesLeft, node->HeaderSpace());
node->SubtractHeaderSpace(willConsume);
node->start -= willConsume;
node->used += willConsume;
bytesLeft -= willConsume;
sizePrepended += willConsume;
} while (bytesLeft > 0);
size_t offset = 0;
node = NULL;
while ((node = (data_node*)list_get_next_item(&buffer->buffers,
node)) != NULL) {
node->offset = offset;
offset += node->used;
}
if (_contiguousBuffer)
*_contiguousBuffer = NULL;
} else {
node->SubtractHeaderSpace(size);
node->start -= size;
node->used += size;
if (_contiguousBuffer)
*_contiguousBuffer = node->start;
while ((node = (data_node*)list_get_next_item(&buffer->buffers, node))
!= NULL) {
node->offset += size;
}
}
buffer->size += size;
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
CHECK_BUFFER(buffer);
return B_OK;
}
static status_t
prepend_data(net_buffer* buffer, const void* data, size_t size)
{
void* contiguousBuffer;
status_t status = prepend_size(buffer, size, &contiguousBuffer);
if (status < B_OK)
return status;
if (contiguousBuffer) {
if (IS_USER_ADDRESS(data)) {
if (user_memcpy(contiguousBuffer, data, size) != B_OK)
return B_BAD_ADDRESS;
} else
memcpy(contiguousBuffer, data, size);
} else
write_data(buffer, 0, data, size);
return B_OK;
}
static status_t
append_size(net_buffer* _buffer, size_t size, void** _contiguousBuffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
data_node* node = (data_node*)list_get_last_item(&buffer->buffers);
if (node == NULL) {
node = add_first_data_node(buffer->allocation_header);
if (node == NULL)
return B_NO_MEMORY;
}
T(AppendSize(buffer, size));
ParanoiaChecker _(buffer);
TRACE(("%d: append_size(buffer %p, size %ld)\n", find_thread(NULL),
buffer, size));
if (node->TailSpace() < size) {
uint32 previousTailSpace = node->TailSpace();
uint32 headerSpace = DATA_NODE_SIZE;
uint32 sizeUsed = MAX_FREE_BUFFER_SIZE - headerSpace;
node->SetTailSpace(0);
node->used += previousTailSpace;
buffer->size += previousTailSpace;
uint32 sizeAdded = previousTailSpace;
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
while (sizeAdded < size) {
if (sizeAdded + sizeUsed > size) {
sizeUsed = size - sizeAdded;
}
data_header* header = create_data_header(headerSpace);
if (header == NULL) {
remove_trailer(buffer, sizeAdded);
return B_NO_MEMORY;
}
node = add_first_data_node(header);
if (node == NULL) {
release_data_header(header);
return B_NO_MEMORY;
}
node->SetTailSpace(node->TailSpace() - sizeUsed);
node->used = sizeUsed;
node->offset = buffer->size;
buffer->size += sizeUsed;
sizeAdded += sizeUsed;
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
list_add_item(&buffer->buffers, node);
release_data_header(header);
}
if (_contiguousBuffer)
*_contiguousBuffer = NULL;
CHECK_BUFFER(buffer);
return B_OK;
}
node->SetTailSpace(node->TailSpace() - size);
if (_contiguousBuffer)
*_contiguousBuffer = node->start + node->used;
node->used += size;
buffer->size += size;
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
CHECK_BUFFER(buffer);
return B_OK;
}
static status_t
append_data(net_buffer* buffer, const void* data, size_t size)
{
size_t used = buffer->size;
void* contiguousBuffer;
status_t status = append_size(buffer, size, &contiguousBuffer);
if (status < B_OK)
return status;
if (contiguousBuffer) {
if (IS_USER_ADDRESS(data)) {
if (user_memcpy(contiguousBuffer, data, size) != B_OK)
return B_BAD_ADDRESS;
} else
memcpy(contiguousBuffer, data, size);
} else
write_data(buffer, used, data, size);
return B_OK;
}
*/
static status_t
remove_header(net_buffer* _buffer, size_t bytes)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
T(RemoveHeader(buffer, bytes));
ParanoiaChecker _(buffer);
if (bytes > buffer->size)
return B_BAD_VALUE;
TRACE(("%d: remove_header(buffer %p, %ld bytes)\n", find_thread(NULL),
buffer, bytes));
size_t left = bytes;
data_node* node = NULL;
while (true) {
node = (data_node*)list_get_first_item(&buffer->buffers);
if (node == NULL) {
if (left == 0)
break;
CHECK_BUFFER(buffer);
return B_ERROR;
}
if (node->used > left)
break;
list_remove_item(&buffer->buffers, node);
left -= node->used;
remove_data_node(node);
node = NULL;
buffer->stored_header_length = 0;
}
if (node != NULL) {
size_t cut = min_c(node->used, left);
node->offset = 0;
node->start += cut;
if ((node->flags & DATA_NODE_STORED_HEADER) != 0)
buffer->stored_header_length += cut;
else
node->AddHeaderSpace(cut);
node->used -= cut;
node = (data_node*)list_get_next_item(&buffer->buffers, node);
}
while (node != NULL) {
node->offset -= bytes;
node = (data_node*)list_get_next_item(&buffer->buffers, node);
}
buffer->size -= bytes;
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
CHECK_BUFFER(buffer);
return B_OK;
}
*/
static status_t
remove_trailer(net_buffer* buffer, size_t bytes)
{
return trim_data(buffer, buffer->size - bytes);
}
the end of the buffer.
*/
static status_t
trim_data(net_buffer* _buffer, size_t newSize)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
TRACE(("%d: trim_data(buffer %p, newSize = %ld, buffer size = %" B_PRIu32 ")\n",
find_thread(NULL), buffer, newSize, buffer->size));
T(Trim(buffer, newSize));
ParanoiaChecker _(buffer);
if (newSize > buffer->size)
return B_BAD_VALUE;
if (newSize == buffer->size)
return B_OK;
data_node* node = get_node_at_offset(buffer, newSize);
if (node == NULL) {
return B_BAD_VALUE;
}
int32 diff = node->used + node->offset - newSize;
node->SetTailSpace(node->TailSpace() + diff);
node->used -= diff;
if (node->used > 0)
node = (data_node*)list_get_next_item(&buffer->buffers, node);
while (node != NULL) {
data_node* next = (data_node*)list_get_next_item(&buffer->buffers, node);
list_remove_item(&buffer->buffers, node);
remove_data_node(node);
node = next;
}
buffer->size = newSize;
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
CHECK_BUFFER(buffer);
return B_OK;
}
clones the data, though, that is the data is not copied, just referenced.
*/
static status_t
append_cloned_data(net_buffer* _buffer, net_buffer* _source, uint32 offset,
size_t bytes)
{
if (bytes == 0)
return B_OK;
net_buffer_private* buffer = (net_buffer_private*)_buffer;
net_buffer_private* source = (net_buffer_private*)_source;
TRACE(("%d: append_cloned_data(buffer %p, source %p, offset = %" B_PRIu32 ", "
"bytes = %ld)\n", find_thread(NULL), buffer, source, offset, bytes));
T(AppendCloned(buffer, source, offset, bytes));
ParanoiaChecker _(buffer);
ParanoiaChecker _2(source);
if (source->size < offset + bytes || source->size < offset)
return B_BAD_VALUE;
data_node* node = get_node_at_offset(source, offset);
if (node == NULL) {
return B_BAD_VALUE;
}
size_t sizeAppended = 0;
while (node != NULL && bytes > 0) {
data_node* clone = add_data_node(buffer, node->header);
if (clone == NULL) {
remove_trailer(buffer, sizeAppended);
return ENOBUFS;
}
if (offset)
offset -= node->offset;
clone->offset = buffer->size;
clone->start = node->start + offset;
clone->used = min_c(bytes, node->used - offset);
if (list_is_empty(&buffer->buffers)) {
buffer->stored_header_length = source->stored_header_length;
clone->flags = node->flags | DATA_NODE_READ_ONLY;
} else
clone->flags = DATA_NODE_READ_ONLY;
list_add_item(&buffer->buffers, clone);
offset = 0;
bytes -= clone->used;
buffer->size += clone->used;
sizeAppended += clone->used;
node = (data_node*)list_get_next_item(&source->buffers, node);
}
if (bytes != 0)
panic("add_cloned_data() failed, bytes != 0!\n");
CHECK_BUFFER(source);
CHECK_BUFFER(buffer);
SET_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, buffer, &buffer->size,
sizeof(buffer->size));
return B_OK;
}
void
set_ancillary_data(net_buffer* buffer, ancillary_data_container* container)
{
((net_buffer_private*)buffer)->ancillary_data = container;
}
ancillary_data_container*
get_ancillary_data(net_buffer* buffer)
{
return ((net_buffer_private*)buffer)->ancillary_data;
}
ancillary data of buffer \c to. Note, that this is the only function that
transfers or copies ancillary data from one buffer to another.
\param from The buffer from which to remove the ancillary data.
\param to The buffer to which to add the ancillary data.
\return A pointer to the first of the moved ancillary data, if any, \c NULL
otherwise.
*/
static void*
transfer_ancillary_data(net_buffer* _from, net_buffer* _to)
{
net_buffer_private* from = (net_buffer_private*)_from;
net_buffer_private* to = (net_buffer_private*)_to;
if (from == NULL || to == NULL)
return NULL;
if (from->ancillary_data == NULL)
return NULL;
if (to->ancillary_data == NULL) {
to->ancillary_data = from->ancillary_data;
from->ancillary_data = NULL;
return next_ancillary_data(to->ancillary_data, NULL, NULL);
}
void* data = move_ancillary_data(from->ancillary_data,
to->ancillary_data);
delete_ancillary_data_container(from->ancillary_data);
from->ancillary_data = NULL;
return data;
}
remove_header(), you can still reclaim it later using restore_header(),
unless you prepended different data (in which case restoring will fail).
*/
status_t
store_header(net_buffer* _buffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
if (node == NULL)
return B_ERROR;
if ((node->flags & DATA_NODE_STORED_HEADER) != 0) {
node->AddHeaderSpace(buffer->stored_header_length);
node->flags &= ~DATA_NODE_STORED_HEADER;
buffer->stored_header_length = 0;
return B_ERROR;
}
buffer->stored_header_length = 0;
node->flags |= DATA_NODE_STORED_HEADER;
return B_OK;
}
ssize_t
stored_header_length(net_buffer* _buffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
if (node == NULL || (node->flags & DATA_NODE_STORED_HEADER) == 0)
return B_BAD_VALUE;
return buffer->stored_header_length;
}
This function does not care whether or not there is a stored header at
all - you have to use the stored_header_length() function to find out.
*/
status_t
restore_header(net_buffer* _buffer, uint32 offset, void* data, size_t bytes)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
if (offset < buffer->stored_header_length) {
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
if (node == NULL
|| offset + bytes > buffer->stored_header_length + buffer->size)
return B_BAD_VALUE;
size_t copied = std::min(bytes, buffer->stored_header_length - offset);
memcpy(data, node->start + offset - buffer->stored_header_length,
copied);
if (copied == bytes)
return B_OK;
data = (uint8*)data + copied;
bytes -= copied;
offset = 0;
} else
offset -= buffer->stored_header_length;
return read_data(_buffer, offset, data, bytes);
}
to the specified target \a buffer.
This function does not care whether or not there is a stored header at
all - you have to use the stored_header_length() function to find out.
*/
status_t
append_restored_header(net_buffer* buffer, net_buffer* _source, uint32 offset,
size_t bytes)
{
net_buffer_private* source = (net_buffer_private*)_source;
if (offset < source->stored_header_length) {
data_node* node = (data_node*)list_get_first_item(&source->buffers);
if (node == NULL
|| offset + bytes > source->stored_header_length + source->size)
return B_BAD_VALUE;
size_t appended = std::min(bytes, source->stored_header_length - offset);
status_t status = append_data(buffer,
node->start + offset - source->stored_header_length, appended);
if (status != B_OK)
return status;
if (appended == bytes)
return B_OK;
bytes -= appended;
offset = 0;
} else
offset -= source->stored_header_length;
return append_cloned_data(buffer, source, offset, bytes);
}
If the space is contiguous, the function will succeed and place a pointer
to that space into \a _contiguousBuffer.
\return B_BAD_VALUE if the offset is outside of the buffer's bounds.
\return B_ERROR in case the buffer is not contiguous at that location.
*/
static status_t
direct_access(net_buffer* _buffer, uint32 offset, size_t size,
void** _contiguousBuffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
ParanoiaChecker _(buffer);
if (offset + size > buffer->size)
return B_BAD_VALUE;
data_node* node = get_node_at_offset(buffer, offset);
if (node == NULL)
return B_BAD_VALUE;
offset -= node->offset;
if (size > node->used - offset)
return B_ERROR;
*_contiguousBuffer = node->start + offset;
return B_OK;
}
static int32
checksum_data(net_buffer* _buffer, uint32 offset, size_t size, bool finalize)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
if (offset + size > buffer->size || size == 0)
return B_BAD_VALUE;
data_node* node = get_node_at_offset(buffer, offset);
if (node == NULL)
return B_ERROR;
offset -= node->offset;
uint32 sum = 0;
while (true) {
size_t bytes = min_c(size, node->used - offset);
if ((offset + node->offset) & 1) {
sum += __swap_int16(compute_checksum(node->start + offset, bytes));
} else
sum += compute_checksum(node->start + offset, bytes);
size -= bytes;
if (size == 0)
break;
offset = 0;
node = (data_node*)list_get_next_item(&buffer->buffers, node);
if (node == NULL)
return B_ERROR;
}
while (sum >> 16) {
sum = (sum & 0xffff) + (sum >> 16);
}
if (!finalize)
return (uint16)sum;
return (uint16)~sum;
}
static uint32
get_iovecs(net_buffer* _buffer, struct iovec* iovecs, uint32 vecCount)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
uint32 count = 0;
while (node != NULL && count < vecCount) {
if (node->used > 0) {
iovecs[count].iov_base = node->start;
iovecs[count].iov_len = node->used;
count++;
}
node = (data_node*)list_get_next_item(&buffer->buffers, node);
}
return count;
}
static uint32
count_iovecs(net_buffer* _buffer)
{
net_buffer_private* buffer = (net_buffer_private*)_buffer;
data_node* node = (data_node*)list_get_first_item(&buffer->buffers);
uint32 count = 0;
while (node != NULL) {
if (node->used > 0)
count++;
node = (data_node*)list_get_next_item(&buffer->buffers, node);
}
return count;
}
static void
swap_addresses(net_buffer* buffer)
{
std::swap(buffer->source, buffer->destination);
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
sNetBufferCache = create_object_cache("net buffer cache",
sizeof(net_buffer_private), 0);
if (sNetBufferCache == NULL)
return B_NO_MEMORY;
sDataNodeCache = create_object_cache("data node cache",
BUFFER_SIZE, 0);
if (sDataNodeCache == NULL) {
delete_object_cache(sNetBufferCache);
return B_NO_MEMORY;
}
#if ENABLE_STATS
add_debugger_command_etc("net_buffer_stats", &dump_net_buffer_stats,
"Print net buffer statistics",
"\nPrint net buffer statistics.\n", 0);
#endif
#if ENABLE_DEBUGGER_COMMANDS
add_debugger_command_etc("net_buffer", &dump_net_buffer,
"Dump net buffer",
"\nDump the net buffer's internal structures.\n", 0);
#endif
return B_OK;
case B_MODULE_UNINIT:
#if ENABLE_STATS
remove_debugger_command("net_buffer_stats", &dump_net_buffer_stats);
#endif
#if ENABLE_DEBUGGER_COMMANDS
remove_debugger_command("net_buffer", &dump_net_buffer);
#endif
delete_object_cache(sNetBufferCache);
delete_object_cache(sDataNodeCache);
return B_OK;
default:
return B_ERROR;
}
}
net_buffer_module_info gNetBufferModule = {
{
NET_BUFFER_MODULE_NAME,
0,
std_ops
},
create_buffer,
free_buffer,
duplicate_buffer,
clone_buffer,
split_buffer,
merge_buffer,
prepend_size,
prepend_data,
append_size,
append_data,
NULL,
NULL,
remove_header,
remove_trailer,
trim_data,
append_cloned_data,
NULL,
set_ancillary_data,
get_ancillary_data,
transfer_ancillary_data,
store_header,
stored_header_length,
restore_header,
append_restored_header,
direct_access,
read_data,
write_data,
checksum_data,
NULL,
get_iovecs,
count_iovecs,
swap_addresses,
dump_buffer,
};