mirror of
https://github.com/RetroShare/RetroShare.git
synced 2025-03-01 03:11:32 -05:00

git-svn-id: http://svn.code.sf.net/p/retroshare/code/branches/v0.5-OpenPGP@5078 b45a01b8-16f6-495d-af2f-9b41ad6348cc
382 lines
13 KiB
C
382 lines
13 KiB
C
/*
|
|
* Copyright (c) 2005-2009 Nominet UK (www.nic.uk)
|
|
* All rights reserved.
|
|
* Contributors: Ben Laurie, Rachel Willmer, Alasdair Mackintosh.
|
|
* The Contributors have asserted their moral rights under the
|
|
* UK Copyright Design and Patents Act 1988 to
|
|
* be recorded as the authors of this copyright work.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
* use this file except in compliance with the License.
|
|
*
|
|
* You may obtain a copy of the License at
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/** Writes data using a series of partial body length headers.
|
|
* (See RFC 4880 4.2.2.4). This is normally used in conjunction
|
|
* with a streaming writer of some kind that needs to write out
|
|
* data packets of unknown length.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#ifndef WIN32
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <openpgpsdk/create.h>
|
|
#include <openpgpsdk/memory.h>
|
|
#include <openpgpsdk/partial.h>
|
|
#include <openpgpsdk/readerwriter.h>
|
|
|
|
static const int debug = 0;
|
|
|
|
#define PACKET_SIZE 2048
|
|
#define MIN_PARTIAL_DATA_LENGTH 512
|
|
#define MAX_PARTIAL_DATA_LENGTH 1073741824
|
|
|
|
typedef struct
|
|
{
|
|
size_t packet_size; // size of packets
|
|
ops_memory_t *buffer; // Data is buffered here until written
|
|
ops_content_tag_t tag; // Packet tag
|
|
ops_memory_t *header; // Header is written here
|
|
ops_boolean_t written_first; // Has the first packet been written?
|
|
ops_write_partial_trailer_t *trailer_fn; // Custom end-of-packet fn
|
|
void *trailer_data; // data for end-of-packet fn
|
|
} stream_partial_arg_t;
|
|
|
|
|
|
|
|
static unsigned int ops_calc_partial_data_length(unsigned int len)
|
|
{
|
|
int i;
|
|
unsigned int mask = MAX_PARTIAL_DATA_LENGTH;
|
|
assert( len > 0 );
|
|
|
|
if (len > MAX_PARTIAL_DATA_LENGTH)
|
|
return MAX_PARTIAL_DATA_LENGTH;
|
|
|
|
for (i = 0 ; i <= 30 ; i++)
|
|
{
|
|
if (mask & len)
|
|
break;
|
|
mask >>= 1;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static ops_boolean_t ops_write_partial_data_length(unsigned int len,
|
|
ops_create_info_t *info)
|
|
{
|
|
// len must be a power of 2 from 0 to 30
|
|
unsigned i;
|
|
unsigned char c[1];
|
|
|
|
for (i = 0 ; i <= 30 ; i++)
|
|
if ((len >> i) & 1)
|
|
break;
|
|
|
|
assert((1u << i) == len);
|
|
|
|
c[0] = 224 + i;
|
|
|
|
return ops_write(c, 1, info);
|
|
}
|
|
|
|
static ops_boolean_t write_partial_data(const unsigned char *data,
|
|
size_t len,
|
|
ops_create_info_t *info)
|
|
{
|
|
if (debug)
|
|
fprintf(stderr, "Writing %zu bytes\n", len);
|
|
while (len > 0)
|
|
{
|
|
size_t pdlen = ops_calc_partial_data_length(len);
|
|
ops_write_partial_data_length(pdlen, info);
|
|
ops_write(data, pdlen, info);
|
|
data += pdlen;
|
|
len -= pdlen;
|
|
}
|
|
return ops_true;
|
|
}
|
|
|
|
static ops_boolean_t write_partial_data_first(stream_partial_arg_t *arg,
|
|
const unsigned char *data,
|
|
unsigned int len,
|
|
ops_create_info_t *info)
|
|
{
|
|
size_t header_len = ops_memory_get_length(arg->header);
|
|
|
|
size_t sz_towrite = len + header_len;
|
|
size_t sz_pd = ops_calc_partial_data_length(sz_towrite);
|
|
size_t first_data_len = (sz_pd - header_len);
|
|
assert(sz_pd >= MIN_PARTIAL_DATA_LENGTH);
|
|
|
|
if (debug)
|
|
fprintf(stderr, "Writing first packet of len %zu (%zu + %u)\n",
|
|
sz_towrite, header_len, len);
|
|
|
|
// Write the packet tag, the partial size and the header, followed
|
|
// by the first chunk of data and then the remainder of the data.
|
|
// (We have to do this in two chunks, as the partial length may not
|
|
// match the number of bytes to write.)
|
|
return ops_write_ptag(arg->tag, info) &&
|
|
ops_write_partial_data_length(sz_pd, info) &&
|
|
ops_write(ops_memory_get_data(arg->header), header_len, info) &&
|
|
ops_write(data, first_data_len, info) &&
|
|
write_partial_data(data + first_data_len, len - first_data_len, info);
|
|
}
|
|
|
|
/*
|
|
* Writes out the last packet. The length is encoded as a fixed-length
|
|
* packet. Note that even if there is no data accumulated in the
|
|
* buffer, we stil lneed to write out a packet, as the final packet in
|
|
* a partially-encoded stream must be a fixed-lngth packet.
|
|
*/
|
|
static ops_boolean_t write_partial_data_last(stream_partial_arg_t *arg,
|
|
ops_create_info_t *info)
|
|
{
|
|
size_t buffer_length = ops_memory_get_length(arg->buffer);
|
|
if (debug)
|
|
fprintf(stderr, "writing final packet of %zu bytes\n", buffer_length);
|
|
return ops_write_length(buffer_length, info) &&
|
|
ops_write(ops_memory_get_data(arg->buffer), buffer_length, info);
|
|
}
|
|
|
|
/*
|
|
* Writes out the data accumulated in the in-memory buffer.
|
|
*/
|
|
static ops_boolean_t flush_buffer(stream_partial_arg_t *arg,
|
|
ops_create_info_t *info)
|
|
{
|
|
ops_boolean_t result = ops_true;
|
|
size_t buffer_length = ops_memory_get_length(arg->buffer);
|
|
if (buffer_length > 0)
|
|
{
|
|
if (debug)
|
|
fprintf(stderr, "Flushing %zu bytes\n", buffer_length);
|
|
|
|
result = write_partial_data(ops_memory_get_data(arg->buffer),
|
|
buffer_length,
|
|
info);
|
|
ops_memory_clear(arg->buffer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static ops_boolean_t stream_partial_writer(const unsigned char *src,
|
|
unsigned length,
|
|
ops_error_t **errors,
|
|
ops_writer_info_t *winfo)
|
|
{
|
|
stream_partial_arg_t *arg = ops_writer_get_arg(winfo);
|
|
|
|
// For the first write operation, we need to write out the header
|
|
// plus the data. The total size that we write out must be at least
|
|
// MIN_PARTIAL_DATA_LENGTH bytes. (See RFC 4880, sec 4.2.2.4,
|
|
// Partial Body Lengths.) If we are given less than this,
|
|
// then we need to store the data in the buffer until we have the
|
|
// minumum
|
|
if (!arg->written_first)
|
|
{
|
|
ops_memory_add(arg->buffer, src, length);
|
|
size_t buffer_length = ops_memory_get_length(arg->buffer);
|
|
size_t header_length = ops_memory_get_length(arg->header);
|
|
if (header_length + buffer_length < MIN_PARTIAL_DATA_LENGTH)
|
|
{
|
|
if (debug)
|
|
fprintf(stderr, "Storing %zu (%zu + %zu) bytes\n",
|
|
header_length + buffer_length, header_length,
|
|
buffer_length);
|
|
return ops_true; // will wait for more data or end of stream
|
|
}
|
|
arg->written_first = ops_true;
|
|
|
|
// Create a writer that will write to the parent stream. Allows
|
|
// useage of ops_write_ptag, etc.
|
|
ops_create_info_t parent_info;
|
|
ops_prepare_parent_info(&parent_info, winfo);
|
|
ops_boolean_t result =
|
|
write_partial_data_first(arg, ops_memory_get_data(arg->buffer),
|
|
buffer_length, &parent_info);
|
|
ops_memory_clear(arg->buffer);
|
|
ops_move_errors(&parent_info, errors);
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
size_t buffer_length = ops_memory_get_length(arg->buffer);
|
|
if (buffer_length + length < arg->packet_size)
|
|
{
|
|
ops_memory_add(arg->buffer, src, length);
|
|
if (debug)
|
|
fprintf(stderr, "Storing %u bytes (total %zu)\n",
|
|
length, buffer_length);
|
|
return ops_true;
|
|
}
|
|
else
|
|
{
|
|
ops_create_info_t parent_info;
|
|
parent_info.winfo = *winfo->next;
|
|
parent_info.errors = *errors;
|
|
return flush_buffer(arg, &parent_info) &&
|
|
write_partial_data(src, length, &parent_info);
|
|
}
|
|
}
|
|
return ops_true;
|
|
}
|
|
|
|
/*
|
|
* Invoked when the total packet size is less than
|
|
* MIN_PARTIAL_DATA_LENGTH. In that case, we write out the whole
|
|
* packet in a single operation, without using partial body length
|
|
* packets.
|
|
*/
|
|
static ops_boolean_t write_complete_packet(stream_partial_arg_t *arg,
|
|
ops_create_info_t *info)
|
|
{
|
|
size_t data_len = ops_memory_get_length(arg->buffer);
|
|
size_t header_len = ops_memory_get_length(arg->header);
|
|
|
|
// Write the header tag, the length of the packet, and the
|
|
// packet. Note that the packet includes the header
|
|
// bytes.
|
|
size_t total = data_len + header_len;
|
|
if (debug)
|
|
fprintf(stderr, "writing entire packet with length %zu (%zu + %zu)\n",
|
|
total, data_len, header_len);
|
|
return ops_write_ptag(arg->tag, info) &&
|
|
ops_write_length(total, info) &&
|
|
ops_write(ops_memory_get_data(arg->header), header_len, info) &&
|
|
ops_write(ops_memory_get_data(arg->buffer), data_len, info);
|
|
}
|
|
|
|
static ops_boolean_t stream_partial_finaliser(ops_error_t **errors,
|
|
ops_writer_info_t *winfo)
|
|
{
|
|
stream_partial_arg_t *arg = ops_writer_get_arg(winfo);
|
|
// write last chunk of data
|
|
|
|
// Create a writer that will write to the parent stream. Allows
|
|
// useage of ops_write_ptag, etc.
|
|
ops_create_info_t parent_info;
|
|
ops_prepare_parent_info(&parent_info, winfo);
|
|
ops_boolean_t result;
|
|
if (!arg->written_first)
|
|
result = write_complete_packet(arg, &parent_info);
|
|
else
|
|
// finish writing
|
|
result = write_partial_data_last(arg, &parent_info);
|
|
if (result && arg->trailer_fn != NULL)
|
|
result = arg->trailer_fn(&parent_info, arg->trailer_data);
|
|
ops_move_errors(&parent_info, errors);
|
|
return result;
|
|
}
|
|
|
|
static void stream_partial_destroyer(ops_writer_info_t *winfo)
|
|
{
|
|
stream_partial_arg_t *arg = ops_writer_get_arg(winfo);
|
|
ops_memory_free(arg->buffer);
|
|
ops_memory_free(arg->header);
|
|
free(arg);
|
|
}
|
|
|
|
/**
|
|
* \ingroup InternalAPI
|
|
* \brief Pushes a partial packet writer onto the stack.
|
|
*
|
|
* This writer is used in conjunction with another writer that
|
|
* generates streaming data of unknown length. The partial writer
|
|
* handles the various partial body length packets. When writing the
|
|
* initial packet header, the partial writer will write out the given
|
|
* tag, write out an initial length, and then invoke the 'header'
|
|
* function to write the remainder of the header. Note that the header
|
|
* function should not write a packet tag or a length.
|
|
*
|
|
* \param packet_size the expected size of the incoming packets. Must
|
|
* be >= 512 bytes. Must be a power of 2. The partial writer
|
|
* will buffer incoming writes into packets of this size. Note
|
|
* that writes will be most efficient if done in chunks of
|
|
* packet_size. If the packet size is unknown, specify 0, and
|
|
* the default size will be used.
|
|
* \param cinfo the writer info
|
|
* \param tag the packet tag
|
|
* \param header_writer a function that writes the packet header.
|
|
* \param header_data passed into header_writer
|
|
*/
|
|
void ops_writer_push_partial(size_t packet_size,
|
|
ops_create_info_t *cinfo,
|
|
ops_content_tag_t tag,
|
|
ops_write_partial_header_t *header_writer,
|
|
void *header_data)
|
|
{
|
|
ops_writer_push_partial_with_trailer(packet_size, cinfo, tag, header_writer,
|
|
header_data, NULL, NULL);
|
|
}
|
|
|
|
/**
|
|
* \ingroup InternalAPI
|
|
* \brief Pushes a partial packet writer onto the stack. Adds a trailer
|
|
* function that will be invoked after writing out the partial
|
|
* packet.
|
|
*
|
|
* This writer is primarily used by the signature writer, which needs
|
|
* to append a signature packet after the literal data packet.
|
|
*
|
|
* \param trailer_writer a function that writes the trailer
|
|
* \param trailer_data passed into trailer_data
|
|
* \see ops_writer_push_partial
|
|
* \see ops_writer_push_signed
|
|
*/
|
|
void ops_writer_push_partial_with_trailer(
|
|
size_t packet_size,
|
|
ops_create_info_t *cinfo,
|
|
ops_content_tag_t tag,
|
|
ops_write_partial_header_t *header_writer,
|
|
void *header_data,
|
|
ops_write_partial_trailer_t *trailer_writer,
|
|
void *trailer_data)
|
|
{
|
|
if (packet_size == 0)
|
|
packet_size = PACKET_SIZE;
|
|
assert(packet_size >= MIN_PARTIAL_DATA_LENGTH);
|
|
// Verify that the packet size is a valid power of 2.
|
|
assert(ops_calc_partial_data_length(packet_size) == packet_size);
|
|
|
|
// Create arg to be used with this writer
|
|
// Remember to free this in the destroyer
|
|
stream_partial_arg_t *arg = ops_mallocz(sizeof *arg);
|
|
arg->tag = tag;
|
|
arg->written_first = ops_false;
|
|
arg->packet_size = packet_size;
|
|
arg->buffer = ops_memory_new();
|
|
ops_memory_init(arg->buffer, arg->packet_size);
|
|
arg->trailer_fn = trailer_writer;
|
|
arg->trailer_data = trailer_data;
|
|
|
|
// Write out the header into the memory buffer. Later we will write
|
|
// this buffer to the underlying output stream.
|
|
ops_create_info_t *header_info;
|
|
ops_setup_memory_write(&header_info, &arg->header, 128);
|
|
header_writer(header_info, header_data);
|
|
ops_writer_close(header_info);
|
|
ops_create_info_delete(header_info);
|
|
|
|
// And push writer on stack
|
|
ops_writer_push(cinfo, stream_partial_writer, stream_partial_finaliser,
|
|
stream_partial_destroyer, arg);
|
|
}
|
|
|
|
// EOF
|