You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
13 KiB
477 lines
13 KiB
/* -*- coding: utf-8 -*-
|
|
* ----------------------------------------------------------------------
|
|
* Copyright © 2011-2012, RedJack, LLC.
|
|
* All rights reserved.
|
|
*
|
|
* Please see the COPYING file in this distribution for license
|
|
* details.
|
|
* ----------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "libcork/core/allocator.h"
|
|
#include "libcork/core/types.h"
|
|
#include "libcork/ds/buffer.h"
|
|
#include "libcork/ds/managed-buffer.h"
|
|
#include "libcork/ds/stream.h"
|
|
#include "libcork/helpers/errors.h"
|
|
|
|
|
|
void
|
|
cork_buffer_init(struct cork_buffer *buffer)
|
|
{
|
|
buffer->buf = NULL;
|
|
buffer->size = 0;
|
|
buffer->allocated_size = 0;
|
|
}
|
|
|
|
|
|
struct cork_buffer *
|
|
cork_buffer_new(void)
|
|
{
|
|
struct cork_buffer *buffer = cork_new(struct cork_buffer);
|
|
cork_buffer_init(buffer);
|
|
return buffer;
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_done(struct cork_buffer *buffer)
|
|
{
|
|
if (buffer->buf != NULL) {
|
|
free(buffer->buf);
|
|
buffer->buf = NULL;
|
|
}
|
|
buffer->size = 0;
|
|
buffer->allocated_size = 0;
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_free(struct cork_buffer *buffer)
|
|
{
|
|
cork_buffer_done(buffer);
|
|
free(buffer);
|
|
}
|
|
|
|
|
|
bool
|
|
cork_buffer_equal(const struct cork_buffer *buffer1,
|
|
const struct cork_buffer *buffer2)
|
|
{
|
|
if (buffer1 == buffer2) {
|
|
return true;
|
|
}
|
|
|
|
if (buffer1->size != buffer2->size) {
|
|
return false;
|
|
}
|
|
|
|
return (memcmp(buffer1->buf, buffer2->buf, buffer1->size) == 0);
|
|
}
|
|
|
|
|
|
static void
|
|
cork_buffer_ensure_size_int(struct cork_buffer *buffer, size_t desired_size)
|
|
{
|
|
size_t new_size;
|
|
|
|
if (CORK_LIKELY(buffer->allocated_size >= desired_size)) {
|
|
return;
|
|
}
|
|
|
|
/* Make sure we at least double the old size when reallocating. */
|
|
new_size = buffer->allocated_size * 2;
|
|
if (desired_size > new_size) {
|
|
new_size = desired_size;
|
|
}
|
|
|
|
buffer->buf = cork_realloc(buffer->buf, new_size);
|
|
buffer->allocated_size = new_size;
|
|
}
|
|
|
|
void
|
|
cork_buffer_ensure_size(struct cork_buffer *buffer, size_t desired_size)
|
|
{
|
|
cork_buffer_ensure_size_int(buffer, desired_size);
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_clear(struct cork_buffer *buffer)
|
|
{
|
|
buffer->size = 0;
|
|
if (buffer->buf != NULL) {
|
|
((char *) buffer->buf)[0] = '\0';
|
|
}
|
|
}
|
|
|
|
void
|
|
cork_buffer_truncate(struct cork_buffer *buffer, size_t length)
|
|
{
|
|
if (buffer->size > length) {
|
|
buffer->size = length;
|
|
if (length == 0) {
|
|
if (buffer->buf != NULL) {
|
|
((char *) buffer->buf)[0] = '\0';
|
|
}
|
|
} else {
|
|
((char *) buffer->buf)[length] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_set(struct cork_buffer *buffer, const void *src, size_t length)
|
|
{
|
|
cork_buffer_ensure_size_int(buffer, length+1);
|
|
memcpy(buffer->buf, src, length);
|
|
((char *) buffer->buf)[length] = '\0';
|
|
buffer->size = length;
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_append(struct cork_buffer *buffer, const void *src, size_t length)
|
|
{
|
|
cork_buffer_ensure_size_int(buffer, buffer->size + length + 1);
|
|
memcpy(buffer->buf + buffer->size, src, length);
|
|
buffer->size += length;
|
|
((char *) buffer->buf)[buffer->size] = '\0';
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_set_string(struct cork_buffer *buffer, const char *str)
|
|
{
|
|
cork_buffer_set(buffer, str, strlen(str));
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_append_string(struct cork_buffer *buffer, const char *str)
|
|
{
|
|
cork_buffer_append(buffer, str, strlen(str));
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_append_vprintf(struct cork_buffer *buffer, const char *format,
|
|
va_list args)
|
|
{
|
|
size_t format_size;
|
|
va_list args1;
|
|
|
|
va_copy(args1, args);
|
|
format_size = vsnprintf(buffer->buf + buffer->size,
|
|
buffer->allocated_size - buffer->size,
|
|
format, args1);
|
|
va_end(args1);
|
|
|
|
/* If the first call works, then set buffer->size and return. */
|
|
if (format_size < (buffer->allocated_size - buffer->size)) {
|
|
buffer->size += format_size;
|
|
return;
|
|
}
|
|
|
|
/* If the first call fails, resize buffer and try again. */
|
|
cork_buffer_ensure_size_int
|
|
(buffer, buffer->allocated_size + format_size + 1);
|
|
format_size = vsnprintf(buffer->buf + buffer->size,
|
|
buffer->allocated_size - buffer->size,
|
|
format, args);
|
|
buffer->size += format_size;
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_vprintf(struct cork_buffer *buffer, const char *format,
|
|
va_list args)
|
|
{
|
|
cork_buffer_clear(buffer);
|
|
cork_buffer_append_vprintf(buffer, format, args);
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_append_printf(struct cork_buffer *buffer, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
cork_buffer_append_vprintf(buffer, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_printf(struct cork_buffer *buffer, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
cork_buffer_vprintf(buffer, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
void
|
|
cork_buffer_append_indent(struct cork_buffer *buffer, size_t indent)
|
|
{
|
|
cork_buffer_ensure_size_int(buffer, buffer->size + indent + 1);
|
|
memset(buffer->buf + buffer->size, ' ', indent);
|
|
buffer->size += indent;
|
|
((char *) buffer->buf)[buffer->size] = '\0';
|
|
}
|
|
|
|
/* including space */
|
|
#define is_sprint(ch) ((ch) >= 0x20 && (ch) <= 0x7e)
|
|
|
|
/* not including space */
|
|
#define is_print(ch) ((ch) > 0x20 && (ch) <= 0x7e)
|
|
|
|
#define is_space(ch) \
|
|
((ch) == ' ' || \
|
|
(ch) == '\f' || \
|
|
(ch) == '\n' || \
|
|
(ch) == '\r' || \
|
|
(ch) == '\t' || \
|
|
(ch) == '\v')
|
|
|
|
#define to_hex(nybble) \
|
|
((nybble) < 10? '0' + (nybble): 'a' - 10 + (nybble))
|
|
|
|
void
|
|
cork_buffer_append_c_string(struct cork_buffer *dest,
|
|
const char *chars, size_t length)
|
|
{
|
|
size_t i;
|
|
cork_buffer_append(dest, "\"", 1);
|
|
for (i = 0; i < length; i++) {
|
|
char ch = chars[i];
|
|
switch (ch) {
|
|
case '\"':
|
|
cork_buffer_append_literal(dest, "\\\"");
|
|
break;
|
|
case '\\':
|
|
cork_buffer_append_literal(dest, "\\\\");
|
|
break;
|
|
case '\f':
|
|
cork_buffer_append_literal(dest, "\\f");
|
|
break;
|
|
case '\n':
|
|
cork_buffer_append_literal(dest, "\\n");
|
|
break;
|
|
case '\r':
|
|
cork_buffer_append_literal(dest, "\\r");
|
|
break;
|
|
case '\t':
|
|
cork_buffer_append_literal(dest, "\\t");
|
|
break;
|
|
case '\v':
|
|
cork_buffer_append_literal(dest, "\\v");
|
|
break;
|
|
default:
|
|
if (is_sprint(ch)) {
|
|
cork_buffer_append(dest, &chars[i], 1);
|
|
} else {
|
|
uint8_t byte = ch;
|
|
cork_buffer_append_printf(dest, "\\x%02" PRIx8, byte);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
cork_buffer_append(dest, "\"", 1);
|
|
}
|
|
|
|
void
|
|
cork_buffer_append_hex_dump(struct cork_buffer *dest, size_t indent,
|
|
const char *chars, size_t length)
|
|
{
|
|
char hex[3 * 16];
|
|
char print[16];
|
|
char *curr_hex = hex;
|
|
char *curr_print = print;
|
|
size_t i;
|
|
size_t column = 0;
|
|
for (i = 0; i < length; i++) {
|
|
char ch = chars[i];
|
|
uint8_t u8 = ch;
|
|
*curr_hex++ = to_hex(u8 >> 4);
|
|
*curr_hex++ = to_hex(u8 & 0x0f);
|
|
*curr_hex++ = ' ';
|
|
*curr_print++ = is_sprint(ch)? ch: '.';
|
|
if (column == 0 && i != 0) {
|
|
cork_buffer_append_literal(dest, "\n");
|
|
cork_buffer_append_indent(dest, indent);
|
|
column++;
|
|
} else if (column == 15) {
|
|
cork_buffer_append_printf
|
|
(dest, "%-48.*s", (int) (curr_hex - hex), hex);
|
|
cork_buffer_append_literal(dest, " |");
|
|
cork_buffer_append(dest, print, curr_print - print);
|
|
cork_buffer_append_literal(dest, "|");
|
|
curr_hex = hex;
|
|
curr_print = print;
|
|
column = 0;
|
|
} else {
|
|
column++;
|
|
}
|
|
}
|
|
|
|
if (column > 0) {
|
|
cork_buffer_append_printf(dest, "%-48.*s", (int) (curr_hex - hex), hex);
|
|
cork_buffer_append_literal(dest, " |");
|
|
cork_buffer_append(dest, print, curr_print - print);
|
|
cork_buffer_append_literal(dest, "|");
|
|
}
|
|
}
|
|
|
|
void
|
|
cork_buffer_append_multiline(struct cork_buffer *dest, size_t indent,
|
|
const char *chars, size_t length)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < length; i++) {
|
|
char ch = chars[i];
|
|
if (ch == '\n') {
|
|
cork_buffer_append_literal(dest, "\n");
|
|
cork_buffer_append_indent(dest, indent);
|
|
} else {
|
|
cork_buffer_append(dest, &chars[i], 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
cork_buffer_append_binary(struct cork_buffer *dest, size_t indent,
|
|
const char *chars, size_t length)
|
|
{
|
|
size_t i;
|
|
bool newline = false;
|
|
|
|
/* If there are any non-printable characters, print out a hex dump */
|
|
for (i = 0; i < length; i++) {
|
|
if (!is_print(chars[i]) && !is_space(chars[i])) {
|
|
cork_buffer_append_literal(dest, "(hex)\n");
|
|
cork_buffer_append_indent(dest, indent);
|
|
cork_buffer_append_hex_dump(dest, indent, chars, length);
|
|
return;
|
|
} else if (chars[i] == '\n') {
|
|
newline = true;
|
|
/* Don't immediately use the multiline format, since there might be
|
|
* a non-printable character later on that kicks us over to the hex
|
|
* dump format. */
|
|
}
|
|
}
|
|
|
|
if (newline) {
|
|
cork_buffer_append_literal(dest, "(multiline)\n");
|
|
cork_buffer_append_indent(dest, indent);
|
|
cork_buffer_append_multiline(dest, indent, chars, length);
|
|
} else {
|
|
cork_buffer_append(dest, chars, length);
|
|
}
|
|
}
|
|
|
|
|
|
struct cork_buffer__managed_buffer {
|
|
struct cork_managed_buffer parent;
|
|
struct cork_buffer *buffer;
|
|
};
|
|
|
|
static void
|
|
cork_buffer__managed_free(struct cork_managed_buffer *vself)
|
|
{
|
|
struct cork_buffer__managed_buffer *self =
|
|
cork_container_of(vself, struct cork_buffer__managed_buffer, parent);
|
|
cork_buffer_free(self->buffer);
|
|
free(self);
|
|
}
|
|
|
|
static struct cork_managed_buffer_iface CORK_BUFFER__MANAGED_BUFFER = {
|
|
cork_buffer__managed_free
|
|
};
|
|
|
|
struct cork_managed_buffer *
|
|
cork_buffer_to_managed_buffer(struct cork_buffer *buffer)
|
|
{
|
|
struct cork_buffer__managed_buffer *self =
|
|
cork_new(struct cork_buffer__managed_buffer);
|
|
self->parent.buf = buffer->buf;
|
|
self->parent.size = buffer->size;
|
|
self->parent.ref_count = 1;
|
|
self->parent.iface = &CORK_BUFFER__MANAGED_BUFFER;
|
|
self->buffer = buffer;
|
|
return &self->parent;
|
|
}
|
|
|
|
|
|
int
|
|
cork_buffer_to_slice(struct cork_buffer *buffer, struct cork_slice *slice)
|
|
{
|
|
struct cork_managed_buffer *managed =
|
|
cork_buffer_to_managed_buffer(buffer);
|
|
|
|
/* We don't have to check for NULL; cork_managed_buffer_slice_offset
|
|
* will do that for us. */
|
|
int rc = cork_managed_buffer_slice_offset(slice, managed, 0);
|
|
|
|
/* Before returning, drop our reference to the managed buffer. If
|
|
* the slicing succeeded, then there will be one remaining reference
|
|
* in the slice. If it didn't succeed, this will free the managed
|
|
* buffer for us. */
|
|
cork_managed_buffer_unref(managed);
|
|
return rc;
|
|
}
|
|
|
|
|
|
struct cork_buffer__stream_consumer {
|
|
struct cork_stream_consumer consumer;
|
|
struct cork_buffer *buffer;
|
|
};
|
|
|
|
static int
|
|
cork_buffer_stream_consumer_data(struct cork_stream_consumer *consumer,
|
|
const void *buf, size_t size,
|
|
bool is_first_chunk)
|
|
{
|
|
struct cork_buffer__stream_consumer *bconsumer = cork_container_of
|
|
(consumer, struct cork_buffer__stream_consumer, consumer);
|
|
|
|
if (is_first_chunk) {
|
|
cork_buffer_clear(bconsumer->buffer);
|
|
}
|
|
|
|
cork_buffer_append(bconsumer->buffer, buf, size);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cork_buffer_stream_consumer_eof(struct cork_stream_consumer *consumer)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
cork_buffer_stream_consumer_free(struct cork_stream_consumer *consumer)
|
|
{
|
|
struct cork_buffer__stream_consumer *bconsumer =
|
|
cork_container_of
|
|
(consumer, struct cork_buffer__stream_consumer, consumer);
|
|
free(bconsumer);
|
|
}
|
|
|
|
struct cork_stream_consumer *
|
|
cork_buffer_to_stream_consumer(struct cork_buffer *buffer)
|
|
{
|
|
struct cork_buffer__stream_consumer *bconsumer =
|
|
cork_new(struct cork_buffer__stream_consumer);
|
|
bconsumer->consumer.data = cork_buffer_stream_consumer_data;
|
|
bconsumer->consumer.eof = cork_buffer_stream_consumer_eof;
|
|
bconsumer->consumer.free = cork_buffer_stream_consumer_free;
|
|
bconsumer->buffer = buffer;
|
|
return &bconsumer->consumer;
|
|
}
|