Blame chunked_ostream.cc

Packit a4aae4
// -*- mode: c++; c-basic-offset:4 -*-
Packit a4aae4
Packit a4aae4
// This file is part of libdap, A C++ implementation of the OPeNDAP Data
Packit a4aae4
// Access Protocol.
Packit a4aae4
Packit a4aae4
// Copyright (c) 2009 OPeNDAP, Inc.
Packit a4aae4
// Author: James Gallagher <jgallagher@opendap.org>
Packit a4aae4
//
Packit a4aae4
// This library is free software; you can redistribute it and/or
Packit a4aae4
// modify it under the terms of the GNU Lesser General Public
Packit a4aae4
// License as published by the Free Software Foundation; either
Packit a4aae4
// version 2.1 of the License, or (at your option) any later version.
Packit a4aae4
//
Packit a4aae4
// This library is distributed in the hope that it will be useful,
Packit a4aae4
// but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit a4aae4
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit a4aae4
// Lesser General Public License for more details.
Packit a4aae4
//
Packit a4aae4
// You should have received a copy of the GNU Lesser General Public
Packit a4aae4
// License along with this library; if not, write to the Free Software
Packit a4aae4
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Packit a4aae4
//
Packit a4aae4
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
Packit a4aae4
//
Packit a4aae4
// Portions of this code were taken verbatim from  Josuttis,
Packit a4aae4
// "The C++ Standard Library," p.672
Packit a4aae4
Packit a4aae4
#include "config.h"
Packit a4aae4
Packit a4aae4
#include <arpa/inet.h>
Packit a4aae4
Packit a4aae4
#include <stdint.h>
Packit a4aae4
Packit a4aae4
#include <string>
Packit a4aae4
#include <streambuf>
Packit a4aae4
Packit a4aae4
#include <cstring>
Packit a4aae4
Packit a4aae4
//#define DODS_DEBUG
Packit a4aae4
Packit a4aae4
#include "chunked_stream.h"
Packit a4aae4
#include "chunked_ostream.h"
Packit a4aae4
#include "debug.h"
Packit a4aae4
Packit a4aae4
namespace libdap {
Packit a4aae4
Packit a4aae4
// flush the characters in the buffer
Packit a4aae4
/**
Packit a4aae4
 * @brief Write out the contents of the buffer as a chunk.
Packit a4aae4
 *
Packit a4aae4
 * @return EOF on error, otherwise the number of bytes in the chunk body.
Packit a4aae4
 */
Packit a4aae4
std::streambuf::int_type
Packit a4aae4
chunked_outbuf::data_chunk()
Packit a4aae4
{
Packit a4aae4
	DBG(cerr << "In chunked_outbuf::data_chunk" << endl);
Packit a4aae4
Packit a4aae4
	int32_t num = pptr() - pbase();	// num needs to be signed for the call to pbump
Packit a4aae4
Packit a4aae4
	// Since this is called by sync() (e.g., flush()), return 0 and do nothing
Packit a4aae4
	// when there's no data to send.
Packit a4aae4
	if (num == 0)
Packit a4aae4
		return 0;
Packit a4aae4
Packit a4aae4
	// here, write out the chunk headers: CHUNKTYPE and CHUNKSIZE
Packit a4aae4
	// as a 32-bit unsigned int. Here I assume that num is never
Packit a4aae4
	// more than 2^24 because that was tested in the constructor
Packit a4aae4
Packit a4aae4
	// Trick: This method always writes CHUNK_DATA type chunks so
Packit a4aae4
	// the chunk type is always 0x00, and given that num never has
Packit a4aae4
	// anything bigger than 24-bits, the high order byte is always
Packit a4aae4
	// 0x00. Of course bit-wise OR with 0x00 isn't going to do
Packit a4aae4
	// much anyway... Here's the general idea all the same:
Packit a4aae4
	//
Packit a4aae4
	// unsigned int chunk_header = (unsigned int)num | CHUNK_type;
Packit a4aae4
	uint32_t header = num;
Packit a4aae4
#if !BYTE_ORDER_PREFIX
Packit a4aae4
	// Add encoding of host's byte order. jhrg 11/24/13
Packit a4aae4
	if (!d_big_endian) header |= CHUNK_LITTLE_ENDIAN;
Packit a4aae4
	// network byte order for the header
Packit a4aae4
	htonl(header);
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
	d_os.write((const char *)&header, sizeof(int32_t));
Packit a4aae4
Packit a4aae4
	// Should bad() throw an error?
Packit a4aae4
	// Are these functions fast or would the bits be faster?
Packit a4aae4
	d_os.write(d_buffer, num);
Packit a4aae4
	if (d_os.eof() || d_os.bad())
Packit a4aae4
		return traits_type::eof();
Packit a4aae4
Packit a4aae4
	pbump(-num);
Packit a4aae4
	return num;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/**
Packit a4aae4
 * @brief Send an end chunk.
Packit a4aae4
 *
Packit a4aae4
 * This is like calling flush_chunk(), but it sends a chunk header with a type of
Packit a4aae4
 * CHUNK_END (instead of CHUNK_DATA). Whatever is in the buffer is written out, but
Packit a4aae4
 * the stream is can be used to send more chunks.
Packit a4aae4
 * @note This is called by the chunked_outbuf destructor, so closing a stream using
Packit a4aae4
 * chunked_outbuf always sends a CHUNK_END type chunk, even if it will have zero
Packit a4aae4
 * bytes
Packit a4aae4
 * @return EOF on error, otherwise the number of bytes sent in the chunk.
Packit a4aae4
 */
Packit a4aae4
std::streambuf::int_type
Packit a4aae4
chunked_outbuf::end_chunk()
Packit a4aae4
{
Packit a4aae4
	DBG(cerr << "In chunked_outbuf::end_chunk" << endl);
Packit a4aae4
Packit a4aae4
	int32_t num = pptr() - pbase();	// num needs to be signed for the call to pbump
Packit a4aae4
Packit a4aae4
	// write out the chunk headers: CHUNKTYPE and CHUNKSIZE
Packit a4aae4
	// as a 32-bit unsigned int. Here I assume that num is never
Packit a4aae4
	// more than 2^24 because that was tested in the constructor
Packit a4aae4
Packit a4aae4
	uint32_t header = (uint32_t)num | CHUNK_END;
Packit a4aae4
Packit a4aae4
#if !BYTE_ORDER_PREFIX
Packit a4aae4
    // Add encoding of host's byte order. jhrg 11/24/13
Packit a4aae4
    if (!d_big_endian) header |= CHUNK_LITTLE_ENDIAN;
Packit a4aae4
    // network byte order for the header
Packit a4aae4
    htonl(header);
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
    // Write out the CHUNK_END header with the byte count.
Packit a4aae4
	// This should be called infrequently, so it's probably not worth
Packit a4aae4
	// optimizing away chunk_header
Packit a4aae4
	d_os.write((const char *)&header, sizeof(uint32_t));
Packit a4aae4
Packit a4aae4
	// Should bad() throw an error?
Packit a4aae4
	// Are these functions fast or would the bits be faster?
Packit a4aae4
	d_os.write(d_buffer, num);
Packit a4aae4
	if (d_os.eof() || d_os.bad())
Packit a4aae4
		return traits_type::eof();
Packit a4aae4
Packit a4aae4
	pbump(-num);
Packit a4aae4
	return num;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/**
Packit a4aae4
 * @brief Send an error chunk
Packit a4aae4
 * While building up the next chunk, send an error chunk, ignoring the data currently
Packit a4aae4
 * write buffer. The buffer is left in a consistent state.
Packit a4aae4
 * @param msg The error message to include in the error chunk
Packit a4aae4
 * @return The number of characters ignored.
Packit a4aae4
 */
Packit a4aae4
std::streambuf::int_type
Packit a4aae4
chunked_outbuf::err_chunk(const std::string &m)
Packit a4aae4
{
Packit a4aae4
	DBG(cerr << "In chunked_outbuf::err_chunk" << endl);
Packit a4aae4
	std::string msg = m;
Packit a4aae4
Packit a4aae4
	// Figure out how many chars are in the buffer - these will be
Packit a4aae4
	// ignored.
Packit a4aae4
	int32_t num = pptr() - pbase();	// num needs to be signed for the call to pbump
Packit a4aae4
Packit a4aae4
	// write out the chunk headers: CHUNKTYPE and CHUNKSIZE
Packit a4aae4
	// as a 32-bit unsigned int. Here I assume that num is never
Packit a4aae4
	// more than 2^24 because that was tested in the constructor
Packit a4aae4
	if (msg.length() > 0x00FFFFFF)
Packit a4aae4
		msg = "Error message too long";
Packit a4aae4
Packit a4aae4
	uint32_t header = (uint32_t)msg.length() | CHUNK_ERR;
Packit a4aae4
Packit a4aae4
#if !BYTE_ORDER_PREFIX
Packit a4aae4
    // Add encoding of host's byte order. jhrg 11/24/13
Packit a4aae4
    if (!d_big_endian) header |= CHUNK_LITTLE_ENDIAN;
Packit a4aae4
    // network byte order for the header
Packit a4aae4
    htonl(header);
Packit a4aae4
#endif
Packit a4aae4
Packit a4aae4
    // Write out the CHUNK_END header with the byte count.
Packit a4aae4
	// This should be called infrequently, so it's probably not worth
Packit a4aae4
	// optimizing away chunk_header
Packit a4aae4
	d_os.write((const char *)&header, sizeof(uint32_t));
Packit a4aae4
Packit a4aae4
	// Should bad() throw an error?
Packit a4aae4
	// Are these functions fast or would the bits be faster?
Packit a4aae4
	d_os.write(msg.data(), msg.length());
Packit a4aae4
	if (d_os.eof() || d_os.bad())
Packit a4aae4
		return traits_type::eof();
Packit a4aae4
Packit a4aae4
	// Reset the buffer pointer, effectively ignoring what's in there now
Packit a4aae4
	pbump(-num);
Packit a4aae4
Packit a4aae4
	// return the number of characters ignored
Packit a4aae4
	return num;
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/**
Packit a4aae4
 * @brief Virtual method called when the internal buffer would overflow.
Packit a4aae4
 * When the internal buffer fills, this method is called by the byte that
Packit a4aae4
 * would cause that overflow. The buffer pointers have been set so that
Packit a4aae4
 * there is actually space for one more character, so \c c can really be
Packit a4aae4
 * sent. Put \c c into the buffer and send it, prefixing the buffer
Packit a4aae4
 * contents with a chunk header.
Packit a4aae4
 * @note This method is called by the std::ostream code.
Packit a4aae4
 * @param c The last character to add to the buffer before sending the
Packit a4aae4
 * next chunk.
Packit a4aae4
 * @return EOF on error, otherwise the value of \c c.
Packit a4aae4
 */
Packit a4aae4
std::streambuf::int_type
Packit a4aae4
chunked_outbuf::overflow(int c)
Packit a4aae4
{
Packit a4aae4
	DBG(cerr << "In chunked_outbuf::overflow" << endl);
Packit a4aae4
Packit a4aae4
	// Note that the buffer and eptr() were set so that when pptr() is
Packit a4aae4
	// at the end of the buffer, there is actually one more character
Packit a4aae4
	// available in the buffer.
Packit a4aae4
	if (!traits_type::eq_int_type(c, traits_type::eof())) {
Packit a4aae4
		*pptr() = traits_type::not_eof(c);
Packit a4aae4
		pbump(1);
Packit a4aae4
	}
Packit a4aae4
	// flush the buffer
Packit a4aae4
	if (data_chunk() == traits_type::eof()) {
Packit a4aae4
		//Error
Packit a4aae4
		return traits_type::eof();
Packit a4aae4
	}
Packit a4aae4
Packit a4aae4
	return traits_type::not_eof(c);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/*
Packit a4aae4
Packit a4aae4
  d_buffer
Packit a4aae4
  |
Packit a4aae4
  v
Packit a4aae4
  |--------------------------------------------|....
Packit a4aae4
  |                                            |   .
Packit a4aae4
  |--------------------------------------------|....
Packit a4aae4
  ^                         ^                   ^
Packit a4aae4
  |                         |                   |
Packit a4aae4
  pbase()                   pptr()              epptr()
Packit a4aae4
Packit a4aae4
 */
Packit a4aae4
Packit a4aae4
/**
Packit a4aae4
 * @brief Write bytes to the chunked stream
Packit a4aae4
 * Write the bytes in \c s to the chunked stream
Packit a4aae4
 * @param s
Packit a4aae4
 * @param num
Packit a4aae4
 * @return The number of bytes written
Packit a4aae4
 */
Packit a4aae4
std::streamsize
Packit a4aae4
chunked_outbuf::xsputn(const char *s, std::streamsize num)
Packit a4aae4
{
Packit a4aae4
	DBG(cerr << "In chunked_outbuf::xsputn: num: " << num << endl);
Packit a4aae4
Packit a4aae4
	// if the current block of data will fit in the buffer, put it there.
Packit a4aae4
	// else, there is at least a complete chunk between what's in the buffer
Packit a4aae4
	// and what's in 's'; send a chunk header, the stuff in the buffer and
Packit a4aae4
	// bytes from 's' to make a complete chunk. Then iterate over 's' sending
Packit a4aae4
	// more chunks until there's less than a complete chunk left in 's'. Put
Packit a4aae4
	// the bytes remaining 's' in the buffer. Return the number of bytes sent
Packit a4aae4
	// or 0 if an error is encountered.
Packit a4aae4
Packit a4aae4
	int32_t bytes_in_buffer = pptr() - pbase();	// num needs to be signed for the call to pbump
Packit a4aae4
Packit a4aae4
	// Will num bytes fit in the buffer? The location of epptr() is one back from
Packit a4aae4
	// the actual end of the buffer, so the next char written will trigger a write
Packit a4aae4
	// of the buffer as a new data chunk.
Packit a4aae4
	if (bytes_in_buffer + num < d_buf_size) {
Packit a4aae4
		DBG2(cerr << ":xsputn: buffering num: " << num << endl);
Packit a4aae4
		memcpy(pptr(), s, num);
Packit a4aae4
		pbump(num);
Packit a4aae4
		return traits_type::not_eof(num);
Packit a4aae4
	}
Packit a4aae4
Packit a4aae4
	// If here, write a chunk header and a chunk's worth of data by combining the
Packit a4aae4
	// data in the buffer and some data from 's'.
Packit a4aae4
	uint32_t header = d_buf_size;
Packit a4aae4
#if !BYTE_ORDER_PREFIX
Packit a4aae4
    // Add encoding of host's byte order. jhrg 11/24/13
Packit a4aae4
    if (!d_big_endian) header |= CHUNK_LITTLE_ENDIAN;
Packit a4aae4
    // network byte order for the header
Packit a4aae4
    htonl(header);
Packit a4aae4
#endif
Packit a4aae4
	d_os.write((const char *)&header, sizeof(int32_t));	// Data chunk's CHUNK_TYPE is 0x00000000
Packit a4aae4
Packit a4aae4
	// Reset the pptr() and epptr() now in case of an error exit. See the 'if'
Packit a4aae4
	// at teh end of this for the only code from here down that will modify the
Packit a4aae4
	// pptr() value.
Packit a4aae4
	setp(d_buffer, d_buffer + (d_buf_size - 1));
Packit a4aae4
Packit a4aae4
	d_os.write(d_buffer, bytes_in_buffer);
Packit a4aae4
	if (d_os.eof() || d_os.bad())
Packit a4aae4
		return traits_type::not_eof(0);
Packit a4aae4
Packit a4aae4
	int bytes_to_fill_out_buffer =  d_buf_size - bytes_in_buffer;
Packit a4aae4
	d_os.write(s, bytes_to_fill_out_buffer);
Packit a4aae4
	if (d_os.eof() || d_os.bad())
Packit a4aae4
		return traits_type::not_eof(0);
Packit a4aae4
	s += bytes_to_fill_out_buffer;
Packit a4aae4
	uint32_t bytes_still_to_send = num - bytes_to_fill_out_buffer;
Packit a4aae4
Packit a4aae4
	// Now send all the remaining data in s until the amount remaining doesn't
Packit a4aae4
	// fill a complete chunk and buffer those data.
Packit a4aae4
	while (bytes_still_to_send >= d_buf_size) {
Packit a4aae4
		// This is header for  a chunk of d_buf_size bytes; the size was set above
Packit a4aae4
		d_os.write((const char *) &header, sizeof(int32_t));
Packit a4aae4
		d_os.write(s, d_buf_size);
Packit a4aae4
		if (d_os.eof() || d_os.bad()) return traits_type::not_eof(0);
Packit a4aae4
		s += d_buf_size;
Packit a4aae4
		bytes_still_to_send -= d_buf_size;
Packit a4aae4
	}
Packit a4aae4
Packit a4aae4
	if (bytes_still_to_send > 0) {
Packit a4aae4
		// if the code is here, one or more chunks have been sent, the
Packit a4aae4
		// buffer is empty and there are < d_buf_size bytes to send. Buffer
Packit a4aae4
		// them.
Packit a4aae4
		memcpy(d_buffer, s, bytes_still_to_send);
Packit a4aae4
		pbump(bytes_still_to_send);
Packit a4aae4
	}
Packit a4aae4
Packit a4aae4
	// Unless an error was detected while writing to the stream, the code must
Packit a4aae4
	// have sent num bytes.
Packit a4aae4
	return traits_type::not_eof(num);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
/**
Packit a4aae4
 * @brief Synchronize the stream with its data sink.
Packit a4aae4
 * @note This method is called by flush() among others
Packit a4aae4
 * @return -1 on error, 0 otherwise.
Packit a4aae4
 */
Packit a4aae4
std::streambuf::int_type
Packit a4aae4
chunked_outbuf::sync()
Packit a4aae4
{
Packit a4aae4
	DBG(cerr << "In chunked_outbuf::sync" << endl);
Packit a4aae4
Packit a4aae4
	if (data_chunk() == traits_type::eof()) {
Packit a4aae4
		// Error
Packit a4aae4
		return traits_type::not_eof(-1);
Packit a4aae4
	}
Packit a4aae4
	return traits_type::not_eof(0);
Packit a4aae4
}
Packit a4aae4
Packit a4aae4
} // namespace libdap