diff --git a/indra/llmessage/llbuffer.cpp b/indra/llmessage/llbuffer.cpp
index e4200b914b31f36564d9a42d1de8cf783ee96d0f..6b0c4f8faba965c4c13baead1ffbe524665f8e61 100644
--- a/indra/llmessage/llbuffer.cpp
+++ b/indra/llmessage/llbuffer.cpp
@@ -65,24 +65,44 @@ S32 LLSegment::size() const
 	return mSize;
 }
 
+bool LLSegment::operator==(const LLSegment& rhs) const
+{
+	if((mData != rhs.mData)||(mSize != rhs.mSize)||(mChannel != rhs.mChannel))
+	{
+		return false;
+	}
+	return true;
+}
 
 /** 
  * LLHeapBuffer
  */
-LLHeapBuffer::LLHeapBuffer() 
+LLHeapBuffer::LLHeapBuffer() :
+	mBuffer(NULL),
+	mSize(0),
+	mNextFree(NULL),
+	mReclaimedBytes(0)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_BUFFER);
 	const S32 DEFAULT_HEAP_BUFFER_SIZE = 16384;
 	allocate(DEFAULT_HEAP_BUFFER_SIZE);
 }
 
-LLHeapBuffer::LLHeapBuffer(S32 size)
+LLHeapBuffer::LLHeapBuffer(S32 size) :
+	mBuffer(NULL),
+	mSize(0),
+	mNextFree(NULL),
+	mReclaimedBytes(0)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_BUFFER);
 	allocate(size);
 }
 
-LLHeapBuffer::LLHeapBuffer(const U8* src, S32 len)
+LLHeapBuffer::LLHeapBuffer(const U8* src, S32 len) :
+	mBuffer(NULL),
+	mSize(0),
+	mNextFree(NULL),
+	mReclaimedBytes(0)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_BUFFER);
 	if((len > 0) && src)
@@ -93,12 +113,6 @@ LLHeapBuffer::LLHeapBuffer(const U8* src, S32 len)
 			memcpy(mBuffer, src, len);	/*Flawfinder: ignore*/
 		}
 	}
-	else
-	{
-		mBuffer = NULL;
-		mSize = 0;
-		mNextFree = NULL;
-	}
 }
 
 // virtual
@@ -111,11 +125,10 @@ LLHeapBuffer::~LLHeapBuffer()
 	mNextFree = NULL;
 }
 
-// virtual
-//S32 LLHeapBuffer::bytesLeft() const
-//{
-//	return (mSize - (mNextFree - mBuffer));
-//}
+S32 LLHeapBuffer::bytesLeft() const
+{
+	return (mSize - (mNextFree - mBuffer));
+}
 
 // virtual
 bool LLHeapBuffer::createSegment(
@@ -139,9 +152,47 @@ bool LLHeapBuffer::createSegment(
 	return true;
 }
 
+// virtual
+bool LLHeapBuffer::reclaimSegment(const LLSegment& segment)
+{
+	if(containsSegment(segment))
+	{
+		mReclaimedBytes += segment.size();
+		if(mReclaimedBytes == mSize)
+		{
+			// We have reclaimed all of the memory from this
+			// buffer. Therefore, we can reset the mNextFree to the
+			// start of the buffer, and reset the reclaimed bytes.
+			mReclaimedBytes = 0;
+			mNextFree = mBuffer;
+		}
+		else if(mReclaimedBytes > mSize)
+		{
+			llwarns << "LLHeapBuffer reclaimed more memory than allocated."
+				<< " This is probably programmer error." << llendl;
+		}
+		return true;
+	}
+	return false;
+}
+
+// virtual
+bool LLHeapBuffer::containsSegment(const LLSegment& segment) const
+{
+	// *NOTE: this check is fairly simple because heap buffers are
+	// simple contiguous chunks of heap memory.
+	if((mBuffer > segment.data())
+	   || ((mBuffer + mSize) < (segment.data() + segment.size())))
+	{
+		return false;
+	}
+	return true;
+}
+
 void LLHeapBuffer::allocate(S32 size)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_BUFFER);
+	mReclaimedBytes = 0;	
 	mBuffer = new U8[size];
 	if(mBuffer)
 	{
@@ -180,6 +231,18 @@ LLChannelDescriptors LLBufferArray::nextChannel()
 	return rv;
 }
 
+S32 LLBufferArray::capacity() const
+{
+	S32 total = 0;
+	const_buffer_iterator_t iter = mBuffers.begin();
+	const_buffer_iterator_t end = mBuffers.end();
+	for(; iter != end; ++iter)
+	{
+		total += (*iter)->capacity();
+	}
+	return total;
+}
+
 bool LLBufferArray::append(S32 channel, const U8* src, S32 len)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_BUFFER);
@@ -684,14 +747,31 @@ LLBufferArray::segment_iterator_t LLBufferArray::makeSegment(
 	return send;
 }
 
-bool LLBufferArray::eraseSegment(const segment_iterator_t& iter)
+bool LLBufferArray::eraseSegment(const segment_iterator_t& erase_iter)
 {
 	LLMemType m1(LLMemType::MTYPE_IO_BUFFER);
-	// *FIX: in theory, we could reclaim the memory. We are leaking a
-	// bit of buffered memory into an unusable but still referenced
-	// location.
-	(void)mSegments.erase(iter);
-	return true;
+
+	// Find out which buffer contains the segment, and if it is found,
+	// ask it to reclaim the memory.
+	bool rv = false;
+	LLSegment segment(*erase_iter);
+	buffer_iterator_t iter = mBuffers.begin();
+	buffer_iterator_t end = mBuffers.end();
+	for(; iter != end; ++iter)
+	{
+		// We can safely call reclaimSegment on every buffer, and once
+		// it returns true, the segment was found.
+		if((*iter)->reclaimSegment(segment))
+		{
+			rv = true;
+			break;
+		}
+	}
+
+	// No need to get the return value since we are not interested in
+	// the interator retured by the call.
+	(void)mSegments.erase(erase_iter);
+	return rv;
 }
 
 
diff --git a/indra/llmessage/llbuffer.h b/indra/llmessage/llbuffer.h
index 3d7f20912332187b94b20d515d800856b1bcbd16..0a0a457d560389aba0cc44b777ab1782fb2c67b0 100644
--- a/indra/llmessage/llbuffer.h
+++ b/indra/llmessage/llbuffer.h
@@ -13,7 +13,7 @@
 
 /**
  * Declaration of classes used for minimizing calls to new[],
- * memcpy(), and delete[]. Typically, you would create an LLHeapArray,
+ * memcpy(), and delete[]. Typically, you would create an LLBufferArray,
  * feed it data, modify and add segments as you process it, and feed
  * it to a sink.
  */
@@ -89,6 +89,16 @@ class LLSegment
 	 */
 	S32 size() const;
 
+	/** 
+	 * @brief Check if two segments are the same.
+	 *
+	 * Two segments are considered equal if they are on the same
+	 * channel and cover the exact same address range.
+	 * @param rhs the segment to compare with this segment.
+	 * @return Returns true if they are equal.
+	 */
+	bool operator==(const LLSegment& rhs) const;
+
 protected:
 	S32 mChannel;
 	U8* mData;
@@ -126,6 +136,35 @@ class LLBuffer
 	 * @return Returns true if a segment was found.
 	 */
 	virtual bool createSegment(S32 channel, S32 size, LLSegment& segment) = 0;
+
+	/** 
+	 * @brief Reclaim a segment from this buffer. 
+	 *
+	 * This method is called on a buffer object when a caller is done
+	 * with a contiguous segment of memory inside this buffer. Since
+	 * segments can be cut arbitrarily outside of the control of the
+	 * buffer, this segment may not match any segment returned from
+	 * <code>createSegment()</code>.  
+	 * @param segment The contiguous buffer segment to reclaim.
+	 * @return Returns true if the call was successful.
+	 */
+	virtual bool reclaimSegment(const LLSegment& segment) = 0;
+
+	/** 
+	 * @brief Test if a segment is inside this buffer.
+	 *
+	 * @param segment The contiguous buffer segment to test.
+	 * @return Returns true if the segment is in the bufffer.
+	 */
+	virtual bool containsSegment(const LLSegment& segment) const = 0;
+
+	/** 
+	 * @brief Return the current number of bytes allocated.
+	 *
+	 * This was implemented as a debugging tool, and it is not
+	 * necessarily a good idea to use it for anything else.
+	 */
+	virtual S32 capacity() const = 0;
 };
 
 /** 
@@ -167,9 +206,11 @@ class LLHeapBuffer : public LLBuffer
 	/** 
 	 * @brief Get the number of bytes left in the buffer.
 	 *
+	 * Note that this is not a virtual function, and only available in
+	 * the LLHeapBuffer as a debugging aid.
 	 * @return Returns the number of bytes left.
 	 */
-	//virtual S32 bytesLeft() const;
+	S32 bytesLeft() const;
 
 	/** 
 	 * @brief Generate a segment for this buffer.
@@ -186,10 +227,40 @@ class LLHeapBuffer : public LLBuffer
 	 */
 	virtual bool createSegment(S32 channel, S32 size, LLSegment& segment);
 
+	/** 
+	 * @brief reclaim a segment from this buffer. 
+	 *
+	 * This method is called on a buffer object when a caller is done
+	 * with a contiguous segment of memory inside this buffer. Since
+	 * segments can be cut arbitrarily outside of the control of the
+	 * buffer, this segment may not match any segment returned from
+	 * <code>createSegment()</code>.  
+	 * This call will fail if the segment passed in is note completely
+	 * inside the buffer, eg, if the segment starts before this buffer
+	 * in memory or ends after it.
+	 * @param segment The contiguous buffer segment to reclaim.
+	 * @return Returns true if the call was successful.
+	 */
+	virtual bool reclaimSegment(const LLSegment& segment);
+
+	/** 
+	 * @brief Test if a segment is inside this buffer.
+	 *
+	 * @param segment The contiguous buffer segment to test.
+	 * @return Returns true if the segment is in the bufffer.
+	 */
+	virtual bool containsSegment(const LLSegment& segment) const;
+
+	/** 
+	 * @brief Return the current number of bytes allocated.
+	 */
+	virtual S32 capacity() const { return mSize; }
+
 protected:
 	U8* mBuffer;
 	S32 mSize;
 	U8* mNextFree;
+	S32 mReclaimedBytes;
 
 private:
 	/** 
@@ -204,13 +275,14 @@ class LLHeapBuffer : public LLBuffer
  * @brief Class to represent scattered memory buffers and in-order segments
  * of that buffered data.
  *
- * NOTE: This class needs to have an iovec interface
+ * *NOTE: This class needs to have an iovec interface
  */
 class LLBufferArray
 {
 public:
 	typedef std::vector<LLBuffer*> buffer_list_t;
 	typedef buffer_list_t::iterator buffer_iterator_t;
+	typedef buffer_list_t::const_iterator const_buffer_iterator_t;
 	typedef std::list<LLSegment> segment_list_t;
 	typedef segment_list_t::const_iterator const_segment_iterator_t;
 	typedef segment_list_t::iterator segment_iterator_t;
@@ -241,11 +313,16 @@ class LLBufferArray
 	 */
 	LLChannelDescriptors nextChannel();
 	//@}
-	
+
 	/* @name Data methods
 	 */
 	//@{
 
+	/** 
+	 * @brief Return the sum of all allocated bytes.
+	 */
+	S32 capacity() const;
+
 	// These methods will be useful once there is any kind of buffer
 	// besides a heap buffer.
 	//bool append(EBufferChannel channel, LLBuffer* data);
@@ -275,7 +352,6 @@ class LLBufferArray
 	 * new segment is created and put in the front of the array. This
 	 * object will internally allocate new buffers if necessary.
 	 * @param channel The channel for this data
-
 	 * @param src The start of memory for the data to be copied
 	 * @param len The number of bytes of data to copy
 	 * @return Returns true if the method worked.
@@ -359,7 +435,7 @@ class LLBufferArray
 	bool takeContents(LLBufferArray& source);
 	//@}
 
-	/* @name Segment methods 
+	/* @name Segment methods
 	 */
 	//@{
 	/** 
@@ -449,7 +525,7 @@ class LLBufferArray
 	 * endSegment() on failure.
 	 */
 	segment_iterator_t makeSegment(S32 channel, S32 length);
-		
+
 	/** 
 	 * @brief Erase the segment if it is in the buffer array.
 	 *
diff --git a/indra/llmessage/llbufferstream.cpp b/indra/llmessage/llbufferstream.cpp
index 684548b4088c2a320ad2a4b9db13a062695ca046..e86620c43890eb9a852ced5f9abb9587ccbacb56 100644
--- a/indra/llmessage/llbufferstream.cpp
+++ b/indra/llmessage/llbufferstream.cpp
@@ -43,34 +43,64 @@ int LLBufferStreamBuf::underflow()
 	{
 		return EOF;
 	}
-	LLSegment segment;
-	LLBufferArray::segment_iterator_t it;
-	U8* last_pos = (U8*)gptr();
-	if(last_pos) --last_pos;
-	
-	LLBufferArray::segment_iterator_t end = mBuffer->endSegment();
 
-	// Get iterator to full segment containing last_pos
-	// and construct sub-segment starting at last_pos.
-	// Note: segment may != *it at this point
-	it = mBuffer->constructSegmentAfter(last_pos, segment);
-	if(it == end)
+	LLBufferArray::segment_iterator_t iter;
+	LLBufferArray::segment_iterator_t end = mBuffer->endSegment();
+	U8* last_pos = (U8*)gptr();
+	LLSegment segment;
+	if(last_pos)
+	{
+		// Back up into a piece of memory we know that we have
+		// allocated so that calls for the next segment based on
+		// 'after' will succeed.
+		--last_pos;
+		iter = mBuffer->splitAfter(last_pos);
+		if(iter != end)
+		{
+			// We need to clear the read segment just in case we have
+			// an early exit in the function and never collect the
+			// next segment. Calling eraseSegment() with the same
+			// segment twice is just like double deleting -- nothing
+			// good comes from it.
+			mBuffer->eraseSegment(iter++);
+			if(iter != end) segment = (*iter);
+		}
+		else
+		{
+			// This should never really happen, but somehow, the
+			// istream is telling the buf that it just finished
+			// reading memory that is not in the buf. I think this
+			// would only happen if there were a bug in the c++ stream
+			// class. Just bail.
+			// *TODO: can we set the fail bit on the stream somehow?
+			return EOF;
+		}
+	}
+	else
+	{
+		// Get iterator to full segment containing last_pos
+		// and construct sub-segment starting at last_pos.
+		// Note: segment may != *it at this point
+		iter = mBuffer->constructSegmentAfter(last_pos, segment);
+	}
+	if(iter == end)
 	{
 		return EOF;
 	}
-	
+
 	// Iterate through segments to find a non-empty segment on input channel.
 	while((!segment.isOnChannel(mChannels.in()) || (segment.size() == 0)))
 	{
-		++it;
-		if(it == end)
+		++iter;
+		if(iter == end)
 		{
 			return EOF;
 		}
 
-		segment = *it;
+		segment = *(iter);
 	}
-	
+
+	// set up the stream to read from the next segment.
 	char* start = (char*)segment.data();
 	setg(start, start, start + segment.size());
 	return *gptr();
@@ -125,12 +155,43 @@ int LLBufferStreamBuf::sync()
 		return return_value;
 	}
 
+	// This chunk of code is not necessary because typically, users of
+	// the stream will read until EOF. Therefore, underflow was called
+	// and the segment was discarded before the sync() was called in
+	// the destructor. Theoretically, we could keep some more data
+	// around and detect the rare case where an istream was deleted
+	// before reading to the end, but that will only leave behind some
+	// unavailable but still referenced memory. Also, if another
+	// istream is constructed, it will re-read that segment, and then
+	// discard it.
+	//U8* last_pos = (U8*)gptr();
+	//if(last_pos)
+	//{
+	//	// Looks like we read something. Discard what we have read.
+	//	// gptr() actually returns the currrent position, but we call
+	//	// it last_pos because of how it is used in the split call
+	//	// below.
+	//	--last_pos;
+	//	LLBufferArray::segment_iterator_t iter;
+	//	iter = mBuffer->splitAfter(last_pos);
+	//	if(iter != mBuffer->endSegment())
+	//	{
+	//		// We need to clear the read segment just in case we have
+	//		// an early exit in the function and never collect the
+	//		// next segment. Calling eraseSegment() with the same
+	//		// segment twice is just like double deleting -- nothing
+	//		// good comes from it.
+	//		mBuffer->eraseSegment(iter);
+	//	}
+	//}
+
 	// set the put pointer so that we force an overflow on the next
 	// write.
 	U8* address = (U8*)pptr();
 	setp(NULL, NULL);
 
-	// *NOTE: I bet we could just --address. Need to think about that.
+	// *NOTE: I bet we could just --address if address is not NULL.
+	// Need to think about that.
 	address = mBuffer->seek(mChannels.out(), address, -1);
 	if(address)
 	{
diff --git a/indra/test/io.cpp b/indra/test/io.cpp
index 4908653f0f96167cb91b64a8b00be7001a21856f..034472fa0b7b4aafdc3916e75cccfaa642910bd8 100644
--- a/indra/test/io.cpp
+++ b/indra/test/io.cpp
@@ -29,6 +29,101 @@
 #include "lluuid.h"
 #include "llinstantmessage.h"
 
+namespace tut
+{
+	struct heap_buffer_data
+	{
+		heap_buffer_data() : mBuffer(NULL) {}
+		~heap_buffer_data() { if(mBuffer) delete mBuffer; }
+		LLHeapBuffer* mBuffer;
+	};
+	typedef test_group<heap_buffer_data> heap_buffer_test;
+	typedef heap_buffer_test::object heap_buffer_object;
+	tut::heap_buffer_test thb("heap_buffer");
+
+	template<> template<>
+	void heap_buffer_object::test<1>()
+	{
+		const S32 BUF_SIZE = 100;
+		mBuffer = new LLHeapBuffer(BUF_SIZE);
+		ensure_equals("empty buffer capacity", mBuffer->capacity(), BUF_SIZE);
+		const S32 SEGMENT_SIZE = 50;
+		LLSegment segment;
+		mBuffer->createSegment(0, SEGMENT_SIZE, segment);
+		ensure_equals("used buffer capacity", mBuffer->capacity(), BUF_SIZE);
+	}
+
+	template<> template<>
+	void heap_buffer_object::test<2>()
+	{
+		const S32 BUF_SIZE = 10;
+		mBuffer = new LLHeapBuffer(BUF_SIZE);
+		LLSegment segment;
+		mBuffer->createSegment(0, BUF_SIZE, segment);
+		ensure("segment is in buffer", mBuffer->containsSegment(segment));
+		ensure_equals("buffer consumed", mBuffer->bytesLeft(), 0);
+		bool  created;
+		created = mBuffer->createSegment(0, 0, segment);
+		ensure("Create zero size segment fails", !created);
+		created = mBuffer->createSegment(0, BUF_SIZE, segment);
+		ensure("Create segment fails", !created);
+	}
+
+	template<> template<>
+	void heap_buffer_object::test<3>()
+	{
+		const S32 BUF_SIZE = 10;
+		mBuffer = new LLHeapBuffer(BUF_SIZE);
+		LLSegment segment;
+		mBuffer->createSegment(0, BUF_SIZE, segment);
+		ensure("segment is in buffer", mBuffer->containsSegment(segment));
+		ensure_equals("buffer consumed", mBuffer->bytesLeft(), 0);
+		bool reclaimed = mBuffer->reclaimSegment(segment);
+		ensure("buffer reclaimed.", reclaimed);
+		ensure_equals("buffer available", mBuffer->bytesLeft(), BUF_SIZE);
+		bool  created;
+		created = mBuffer->createSegment(0, 0, segment);
+		ensure("Create zero size segment fails", !created);
+		created = mBuffer->createSegment(0, BUF_SIZE, segment);
+		ensure("Create another segment succeeds", created);
+	}
+
+	template<> template<>
+	void heap_buffer_object::test<4>()
+	{
+		const S32 BUF_SIZE = 10;
+		const S32 SEGMENT_SIZE = 4;
+		mBuffer = new LLHeapBuffer(BUF_SIZE);
+		LLSegment seg1;
+		mBuffer->createSegment(0, SEGMENT_SIZE, seg1);
+		ensure("segment is in buffer", mBuffer->containsSegment(seg1));
+		LLSegment seg2;
+		mBuffer->createSegment(0, SEGMENT_SIZE, seg2);
+		ensure("segment is in buffer", mBuffer->containsSegment(seg2));
+		LLSegment seg3;
+		mBuffer->createSegment(0, SEGMENT_SIZE, seg3);
+		ensure("segment is in buffer", mBuffer->containsSegment(seg3));
+		ensure_equals("segment is truncated", seg3.size(), 2);
+		LLSegment seg4;
+		bool created;
+		created = mBuffer->createSegment(0, SEGMENT_SIZE, seg4);
+		ensure("Create segment fails", !created);
+		bool reclaimed;
+		reclaimed = mBuffer->reclaimSegment(seg1);
+		ensure("buffer reclaim succeed.", reclaimed);
+		ensure_equals("no buffer available", mBuffer->bytesLeft(), 0);
+		reclaimed = mBuffer->reclaimSegment(seg2);
+		ensure("buffer reclaim succeed.", reclaimed);
+		ensure_equals("buffer reclaimed", mBuffer->bytesLeft(), 0);
+		reclaimed = mBuffer->reclaimSegment(seg3);
+		ensure("buffer reclaim succeed.", reclaimed);
+		ensure_equals("buffer reclaimed", mBuffer->bytesLeft(), BUF_SIZE);
+		created = mBuffer->createSegment(0, SEGMENT_SIZE, seg1);
+		ensure("segment is in buffer", mBuffer->containsSegment(seg1));
+		ensure("Create segment succeds", created);
+	}
+}
+
 namespace tut
 {
 	struct buffer_data
@@ -209,6 +304,37 @@ namespace tut
 		delete[] temp;
 	}
 
+	template<> template<>
+	void buffer_object::test<9>()
+	{
+		LLChannelDescriptors ch = mBuffer.nextChannel();
+		mBuffer.append(ch.in(), (U8*)"1", 1);
+		S32 capacity = mBuffer.capacity();
+		ensure("has capacity", capacity > 0);
+		U8* temp = new U8[capacity - 1];
+		mBuffer.append(ch.in(), temp, capacity - 1);
+		capacity = mBuffer.capacity();
+		ensure("has capacity when full", capacity > 0);
+		S32 used = mBuffer.countAfter(ch.in(), NULL);
+		ensure_equals("used equals capacity", used, capacity);
+
+		LLBufferArray::segment_iterator_t iter = mBuffer.beginSegment();
+		while(iter != mBuffer.endSegment())
+		{
+			mBuffer.eraseSegment(iter++);
+		}
+
+		used = mBuffer.countAfter(ch.in(), NULL);
+		ensure_equals("used is zero", used, 0);
+		S32 capacity2 = mBuffer.capacity();
+		ensure_equals("capacity the same after erase", capacity2, capacity);
+		mBuffer.append(ch.in(), temp, capacity - 1);
+		capacity2 = mBuffer.capacity();
+		ensure_equals("capacity the same after append", capacity2, capacity);
+
+		delete[] temp;
+	}
+
 #if 0
 	template<> template<>
 	void buffer_object::test<9>()
@@ -268,7 +394,7 @@ namespace tut
 	void bas_object::test<1>()
 	{
 		const char HELLO_WORLD[] = "hello world";
-		const S32 str_len = strlen(HELLO_WORLD);		/* Flawfinder: ignore */
+		const S32 str_len = strlen(HELLO_WORLD);	/* Flawfinder: ignore */
 		LLChannelDescriptors ch = mBuffer.nextChannel();
 		LLBufferStream str(ch, &mBuffer);
 		mBuffer.append(ch.in(), (U8*)HELLO_WORLD, str_len);