Skip to content
Snippets Groups Projects
Commit 4da93b6a authored by Monty Brandenberg's avatar Monty Brandenberg
Browse files

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.
parent a066bc19
No related branches found
No related tags found
No related merge requests found
...@@ -24,6 +24,7 @@ include_directories( ...@@ -24,6 +24,7 @@ include_directories(
set(llcorehttp_SOURCE_FILES set(llcorehttp_SOURCE_FILES
bufferarray.cpp bufferarray.cpp
bufferstream.cpp
httpcommon.cpp httpcommon.cpp
httpheaders.cpp httpheaders.cpp
httpoptions.cpp httpoptions.cpp
...@@ -47,6 +48,7 @@ set(llcorehttp_HEADER_FILES ...@@ -47,6 +48,7 @@ set(llcorehttp_HEADER_FILES
CMakeLists.txt CMakeLists.txt
bufferarray.h bufferarray.h
bufferstream.h
httpcommon.h httpcommon.h
httphandler.h httphandler.h
httpheaders.h httpheaders.h
...@@ -105,6 +107,7 @@ if (LL_TESTS) ...@@ -105,6 +107,7 @@ if (LL_TESTS)
tests/test_httprequestqueue.hpp tests/test_httprequestqueue.hpp
tests/test_httpheaders.hpp tests/test_httpheaders.hpp
tests/test_bufferarray.hpp tests/test_bufferarray.hpp
tests/test_bufferstream.hpp
) )
set_source_files_properties(${llcorehttp_TEST_HEADER_FILES} set_source_files_properties(${llcorehttp_TEST_HEADER_FILES}
......
...@@ -288,6 +288,20 @@ int BufferArray::findBlock(size_t pos, size_t * ret_offset) ...@@ -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 // BufferArray::Block Definitions
// ================================== // ==================================
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
namespace LLCore namespace LLCore
{ {
class BufferArrayStreamBuf;
/// A very simple scatter/gather type map for bulk data. The motivation /// A very simple scatter/gather type map for bulk data. The motivation
/// for this class is the writedata callback used by libcurl. Response /// for this class is the writedata callback used by libcurl. Response
...@@ -65,6 +66,11 @@ namespace LLCore ...@@ -65,6 +66,11 @@ namespace LLCore
class BufferArray : public LLCoreInt::RefCounted class BufferArray : public LLCoreInt::RefCounted
{ {
public: 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(); BufferArray();
protected: protected:
...@@ -114,6 +120,8 @@ class BufferArray : public LLCoreInt::RefCounted ...@@ -114,6 +120,8 @@ class BufferArray : public LLCoreInt::RefCounted
protected: protected:
int findBlock(size_t pos, size_t * ret_offset); int findBlock(size_t pos, size_t * ret_offset);
bool getBlockStartEnd(int block, const char ** start, const char ** end);
protected: protected:
class Block; class Block;
......
/**
* @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
/**
* @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_
...@@ -36,12 +36,13 @@ ...@@ -36,12 +36,13 @@
#include "../test/lltut.h" #include "../test/lltut.h"
// Pull in each of the test sets // Pull in each of the test sets
#include "test_bufferarray.hpp"
#include "test_bufferstream.hpp"
#include "test_httpstatus.hpp" #include "test_httpstatus.hpp"
#include "test_refcounted.hpp" #include "test_refcounted.hpp"
#include "test_httpoperation.hpp" #include "test_httpoperation.hpp"
#include "test_httprequest.hpp" #include "test_httprequest.hpp"
#include "test_httpheaders.hpp" #include "test_httpheaders.hpp"
#include "test_bufferarray.hpp"
#include "test_httprequestqueue.hpp" #include "test_httprequestqueue.hpp"
#include "llproxy.h" #include "llproxy.h"
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
#include "test_allocator.h" #include "test_allocator.h"
using namespace LLCoreInt; using namespace LLCore;
......
/**
* @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_
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment