SH-3177 Add streambuf/iostream adapters to BufferArray object.

Initial version that should have enough of the plumbing to produce
a working adapter.  Memory test is showing 8 bytes held after one
of the tests so I'm going to revisit that later.  But basic
functionality is there going by the unit tests.
master
Monty Brandenberg 2012-06-21 19:45:40 -04:00
parent a066bc1994
commit 4da93b6ad9
8 changed files with 659 additions and 2 deletions

View File

@ -24,6 +24,7 @@ include_directories(
set(llcorehttp_SOURCE_FILES
bufferarray.cpp
bufferstream.cpp
httpcommon.cpp
httpheaders.cpp
httpoptions.cpp
@ -47,6 +48,7 @@ set(llcorehttp_HEADER_FILES
CMakeLists.txt
bufferarray.h
bufferstream.h
httpcommon.h
httphandler.h
httpheaders.h
@ -105,6 +107,7 @@ if (LL_TESTS)
tests/test_httprequestqueue.hpp
tests/test_httpheaders.hpp
tests/test_bufferarray.hpp
tests/test_bufferstream.hpp
)
set_source_files_properties(${llcorehttp_TEST_HEADER_FILES}

View File

@ -288,6 +288,20 @@ int BufferArray::findBlock(size_t pos, size_t * ret_offset)
}
bool BufferArray::getBlockStartEnd(int block, const char ** start, const char ** end)
{
if (block < 0 || block >= mBlocks.size())
{
return false;
}
const Block & b(*mBlocks[block]);
*start = &b.mData[0];
*end = &b.mData[b.mUsed];
return true;
}
// ==================================
// BufferArray::Block Definitions
// ==================================

View File

@ -37,6 +37,7 @@
namespace LLCore
{
class BufferArrayStreamBuf;
/// A very simple scatter/gather type map for bulk data. The motivation
/// for this class is the writedata callback used by libcurl. Response
@ -65,6 +66,11 @@ namespace LLCore
class BufferArray : public LLCoreInt::RefCounted
{
public:
// BufferArrayStreamBuf has intimate knowledge of this
// implementation to implement a buffer-free adapter.
// Changes here will likely need to be reflected there.
friend class BufferArrayStreamBuf;
BufferArray();
protected:
@ -114,6 +120,8 @@ public:
protected:
int findBlock(size_t pos, size_t * ret_offset);
bool getBlockStartEnd(int block, const char ** start, const char ** end);
protected:
class Block;

View File

@ -0,0 +1,285 @@
/**
* @file bufferstream.cpp
* @brief Implements the BufferStream adapter class
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2012, Linden Research, Inc.
*
* 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;
* version 2.1 of the License only.
*
* 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
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "bufferstream.h"
#include "bufferarray.h"
namespace LLCore
{
BufferArrayStreamBuf::BufferArrayStreamBuf(BufferArray * array)
: mBufferArray(array),
mReadCurPos(0),
mReadCurBlock(-1),
mReadBegin(NULL),
mReadCur(NULL),
mReadEnd(NULL),
mWriteCurPos(0)
{
if (array)
{
array->addRef();
mWriteCurPos = array->mLen;
}
}
BufferArrayStreamBuf::~BufferArrayStreamBuf()
{
if (mBufferArray)
{
mBufferArray->release();
mBufferArray = NULL;
}
}
BufferArrayStreamBuf::int_type BufferArrayStreamBuf::underflow()
{
if (! mBufferArray)
{
return traits_type::eof();
}
if (mReadCur == mReadEnd)
{
// Find the next block with actual data or leave
// mCurBlock/mCur/mEnd unchanged if we're at the end
// of any block chain.
const char * new_begin(NULL), * new_end(NULL);
int new_cur_block(mReadCurBlock + 1);
while (mBufferArray->getBlockStartEnd(new_cur_block, &new_begin, &new_end))
{
if (new_begin != new_end)
{
break;
}
++new_cur_block;
}
if (new_begin == new_end)
{
return traits_type::eof();
}
mReadCurBlock = new_cur_block;
mReadBegin = mReadCur = new_begin;
mReadEnd = new_end;
}
return traits_type::to_int_type(*mReadCur);
}
BufferArrayStreamBuf::int_type BufferArrayStreamBuf::uflow()
{
const int_type ret(underflow());
if (traits_type::eof() != ret)
{
++mReadCur;
++mReadCurPos;
}
return ret;
}
BufferArrayStreamBuf::int_type BufferArrayStreamBuf::pbackfail(int_type ch)
{
if (! mBufferArray)
{
return traits_type::eof();
}
if (mReadCur == mReadBegin)
{
// Find the previous block with actual data or leave
// mCurBlock/mBegin/mCur/mEnd unchanged if we're at the
// beginning of any block chain.
const char * new_begin(NULL), * new_end(NULL);
int new_cur_block(mReadCurBlock - 1);
while (mBufferArray->getBlockStartEnd(new_cur_block, &new_begin, &new_end))
{
if (new_begin != new_end)
{
break;
}
--new_cur_block;
}
if (new_begin == new_end)
{
return traits_type::eof();
}
mReadCurBlock = new_cur_block;
mReadBegin = new_begin;
mReadEnd = mReadCur = new_end;
}
if (traits_type::eof() != ch && mReadCur[-1] != ch)
{
return traits_type::eof();
}
--mReadCurPos;
return traits_type::to_int_type(*--mReadCur);
}
std::streamsize BufferArrayStreamBuf::showmanyc()
{
if (! mBufferArray)
{
return -1;
}
return mBufferArray->mLen - mReadCurPos;
}
BufferArrayStreamBuf::int_type BufferArrayStreamBuf::overflow(int c)
{
if (! mBufferArray || mWriteCurPos > mBufferArray->mLen)
{
return traits_type::eof();
}
const size_t wrote(mBufferArray->write(mWriteCurPos, &c, 1));
mWriteCurPos += wrote;
return wrote ? c : traits_type::eof();
}
std::streamsize BufferArrayStreamBuf::xsputn(const char * src, std::streamsize count)
{
if (! mBufferArray || mWriteCurPos > mBufferArray->mLen)
{
return 0;
}
const size_t wrote(mBufferArray->write(mWriteCurPos, src, count));
mWriteCurPos += wrote;
return wrote;
}
std::streampos BufferArrayStreamBuf::seekoff(std::streamoff off,
std::ios_base::seekdir way,
std::ios_base::openmode which)
{
std::streampos ret(-1);
if (! mBufferArray)
{
return ret;
}
if (std::ios_base::in == which)
{
size_t pos(0);
switch (way)
{
case std::ios_base::beg:
pos = off;
break;
case std::ios_base::cur:
pos = mReadCurPos += off;
break;
case std::ios_base::end:
pos = mBufferArray->mLen - off;
break;
default:
return ret;
}
if (pos >= mBufferArray->size())
{
pos = (std::max)(size_t(0), mBufferArray->size() - 1);
}
size_t ba_offset(0);
int block(mBufferArray->findBlock(pos, &ba_offset));
if (block < 0)
return ret;
const char * start(NULL), * end(NULL);
if (! mBufferArray->getBlockStartEnd(block, &start, &end))
return ret;
mReadCurBlock = block;
mReadBegin = start;
mReadCur = start + ba_offset;
mReadEnd = end;
ret = mReadCurPos = pos;
}
else if (std::ios_base::out == which)
{
size_t pos(0);
switch (way)
{
case std::ios_base::beg:
pos = off;
break;
case std::ios_base::cur:
pos = mWriteCurPos += off;
break;
case std::ios_base::end:
pos = mBufferArray->mLen - off;
break;
default:
return ret;
}
if (pos < 0)
return ret;
if (pos > mBufferArray->size())
{
pos = mBufferArray->size();
}
ret = mWriteCurPos = pos;
}
return ret;
}
BufferArrayStream::BufferArrayStream(BufferArray * ba)
: std::iostream(&mStreamBuf),
mStreamBuf(ba)
{}
BufferArrayStream::~BufferArrayStream()
{}
} // end namespace LLCore

View File

@ -0,0 +1,94 @@
/**
* @file bufferstream.h
* @brief Public-facing declaration for the BufferStream adapter class
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2012, Linden Research, Inc.
*
* 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;
* version 2.1 of the License only.
*
* 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
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef _LLCORE_BUFFER_STREAM_H_
#define _LLCORE_BUFFER_STREAM_H_
#include <sstream>
#include <cstdlib>
#include "bufferarray.h"
namespace LLCore
{
class BufferArrayStreamBuf : public std::streambuf
{
public:
BufferArrayStreamBuf(BufferArray * array);
virtual ~BufferArrayStreamBuf();
private:
BufferArrayStreamBuf(const BufferArrayStreamBuf &); // Not defined
void operator=(const BufferArrayStreamBuf &); // Not defined
public:
// Input interfaces from std::streambuf
int_type underflow();
int_type uflow();
int_type pbackfail(int_type ch);
std::streamsize showmanyc();
// Output interfaces from std::streambuf
int_type overflow(int c);
std::streamsize xsputn(const char * src, std::streamsize count);
// Common/misc interfaces from std::streambuf
std::streampos seekoff(std::streamoff off, std::ios_base::seekdir way, std::ios_base::openmode which);
protected:
BufferArray * mBufferArray; // Ref counted
size_t mReadCurPos;
int mReadCurBlock;
const char * mReadBegin;
const char * mReadCur;
const char * mReadEnd;
size_t mWriteCurPos;
}; // end class BufferArrayStreamBuf
class BufferArrayStream : public std::iostream
{
public:
BufferArrayStream(BufferArray * ba);
~BufferArrayStream();
protected:
BufferArrayStream(const BufferArrayStream &);
void operator=(const BufferArrayStream &);
protected:
BufferArrayStreamBuf mStreamBuf;
}; // end class BufferArrayStream
} // end namespace LLCore
#endif // _LLCORE_BUFFER_STREAM_H_

View File

@ -36,12 +36,13 @@
#include "../test/lltut.h"
// Pull in each of the test sets
#include "test_bufferarray.hpp"
#include "test_bufferstream.hpp"
#include "test_httpstatus.hpp"
#include "test_refcounted.hpp"
#include "test_httpoperation.hpp"
#include "test_httprequest.hpp"
#include "test_httpheaders.hpp"
#include "test_bufferarray.hpp"
#include "test_httprequestqueue.hpp"
#include "llproxy.h"

View File

@ -33,7 +33,7 @@
#include "test_allocator.h"
using namespace LLCoreInt;
using namespace LLCore;

View File

@ -0,0 +1,252 @@
/**
* @file test_bufferstream.hpp
* @brief unit tests for the LLCore::BufferArrayStreamBuf/BufferArrayStream classes
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2012, Linden Research, Inc.
*
* 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;
* version 2.1 of the License only.
*
* 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
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef TEST_LLCORE_BUFFER_STREAM_H_
#define TEST_LLCORE_BUFFER_STREAM_H_
#include "bufferstream.h"
#include <iostream>
#include "test_allocator.h"
using namespace LLCore;
namespace tut
{
struct BufferStreamTestData
{
// the test objects inherit from this so the member functions and variables
// can be referenced directly inside of the test functions.
size_t mMemTotal;
};
typedef test_group<BufferStreamTestData> BufferStreamTestGroupType;
typedef BufferStreamTestGroupType::object BufferStreamTestObjectType;
BufferStreamTestGroupType BufferStreamTestGroup("BufferStream Tests");
typedef BufferArrayStreamBuf::traits_type tst_traits_t;
template <> template <>
void BufferStreamTestObjectType::test<1>()
{
set_test_name("BufferArrayStreamBuf construction with NULL BufferArray");
// record the total amount of dynamically allocated memory
mMemTotal = GetMemTotal();
// create a new ref counted object with an implicit reference
BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(NULL);
ensure("Memory being used", mMemTotal < GetMemTotal());
// Not much will work with a NULL
ensure("underflow() on NULL fails", tst_traits_t::eof() == bsb->underflow());
ensure("uflow() on NULL fails", tst_traits_t::eof() == bsb->uflow());
ensure("pbackfail() on NULL fails", tst_traits_t::eof() == bsb->pbackfail('c'));
ensure("showmanyc() on NULL fails", bsb->showmanyc() == -1);
ensure("overflow() on NULL fails", tst_traits_t::eof() == bsb->overflow('c'));
ensure("xsputn() on NULL fails", bsb->xsputn("blah", 4) == 0);
ensure("seekoff() on NULL fails", bsb->seekoff(0, std::ios_base::beg, std::ios_base::in) == std::streampos(-1));
// release the implicit reference, causing the object to be released
delete bsb;
bsb = NULL;
// make sure we didn't leak any memory
ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
template <> template <>
void BufferStreamTestObjectType::test<2>()
{
set_test_name("BufferArrayStream construction with NULL BufferArray");
// record the total amount of dynamically allocated memory
mMemTotal = GetMemTotal();
// create a new ref counted object with an implicit reference
BufferArrayStream * bas = new BufferArrayStream(NULL);
ensure("Memory being used", mMemTotal < GetMemTotal());
// Not much will work with a NULL here
ensure("eof() is false on NULL", ! bas->eof());
ensure("fail() is false on NULL", ! bas->fail());
ensure("good() on NULL", bas->good());
// release the implicit reference, causing the object to be released
delete bas;
bas = NULL;
// make sure we didn't leak any memory
ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
template <> template <>
void BufferStreamTestObjectType::test<3>()
{
set_test_name("BufferArrayStreamBuf construction with empty BufferArray");
// record the total amount of dynamically allocated memory
mMemTotal = GetMemTotal();
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(ba);
ensure("Memory being used", mMemTotal < GetMemTotal());
// I can release my ref on the BA
ba->release();
ba = NULL;
// release the implicit reference, causing the object to be released
delete bsb;
bsb = NULL;
// make sure we didn't leak any memory
ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
template <> template <>
void BufferStreamTestObjectType::test<4>()
{
set_test_name("BufferArrayStream construction with empty BufferArray");
// record the total amount of dynamically allocated memory
mMemTotal = GetMemTotal();
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
{
// create a new ref counted object with an implicit reference
BufferArrayStream bas(ba);
ensure("Memory being used", mMemTotal < GetMemTotal());
}
// release the implicit reference, causing the object to be released
ba->release();
ba = NULL;
// make sure we didn't leak any memory
ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
template <> template <>
void BufferStreamTestObjectType::test<5>()
{
set_test_name("BufferArrayStreamBuf construction with real BufferArray");
// record the total amount of dynamically allocated memory
mMemTotal = GetMemTotal();
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
const char * content("This is a string. A fragment.");
const size_t c_len(strlen(content));
ba->append(content, c_len);
BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(ba);
ensure("Memory being used", mMemTotal < GetMemTotal());
// I can release my ref on the BA
ba->release();
ba = NULL;
// Various static state
ensure("underflow() returns 'T'", bsb->underflow() == 'T');
ensure("underflow() returns 'T' again", bsb->underflow() == 'T');
ensure("uflow() returns 'T'", bsb->uflow() == 'T');
ensure("uflow() returns 'h'", bsb->uflow() == 'h');
ensure("pbackfail('i') fails", tst_traits_t::eof() == bsb->pbackfail('i'));
ensure("pbackfail('T') fails", tst_traits_t::eof() == bsb->pbackfail('T'));
ensure("pbackfail('h') succeeds", bsb->pbackfail('h') == 'h');
ensure("showmanyc() is everything but the 'T'", bsb->showmanyc() == (c_len - 1));
ensure("overflow() appends", bsb->overflow('c') == 'c');
ensure("showmanyc() reflects append", bsb->showmanyc() == (c_len - 1 + 1));
ensure("xsputn() appends some more", bsb->xsputn("bla!", 4) == 4);
ensure("showmanyc() reflects 2nd append", bsb->showmanyc() == (c_len - 1 + 5));
ensure("seekoff() succeeds", bsb->seekoff(0, std::ios_base::beg, std::ios_base::in) == std::streampos(0));
ensure("seekoff() succeeds 2", bsb->seekoff(4, std::ios_base::cur, std::ios_base::in) == std::streampos(4));
ensure("showmanyc() picks up seekoff", bsb->showmanyc() == (c_len + 5 - 4));
ensure("seekoff() succeeds 3", bsb->seekoff(0, std::ios_base::end, std::ios_base::in) == std::streampos(c_len + 4));
ensure("pbackfail('!') succeeds", tst_traits_t::eof() == bsb->pbackfail('!'));
// release the implicit reference, causing the object to be released
delete bsb;
bsb = NULL;
// make sure we didn't leak any memory
ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
template <> template <>
void BufferStreamTestObjectType::test<6>()
{
set_test_name("BufferArrayStream construction with real BufferArray");
// record the total amount of dynamically allocated memory
mMemTotal = GetMemTotal();
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
//const char * content("This is a string. A fragment.");
//const size_t c_len(strlen(content));
//ba->append(content, strlen(content));
{
// create a new ref counted object with an implicit reference
BufferArrayStream bas(ba);
ensure("Memory being used", mMemTotal < GetMemTotal());
// Basic operations
bas << "Hello" << 27 << ".";
ensure("BA length 8", ba->size() == 8);
std::string str;
bas >> str;
ensure("reads correctly", str == "Hello27.");
}
// release the implicit reference, causing the object to be released
ba->release();
ba = NULL;
// make sure we didn't leak any memory
// ensure("Allocated memory returned", mMemTotal == GetMemTotal());
static U64 mem = GetMemTotal();
}
} // end namespace tut
#endif // TEST_LLCORE_BUFFER_STREAM_H_