encoding: don't call flush() in OBufferStream if already closed

Refs: #5240
Change-Id: I48e2694657971960079c5b07b35102d32c316bc1
diff --git a/ndn-cxx/encoding/buffer-stream.cpp b/ndn-cxx/encoding/buffer-stream.cpp
index bf363c5..6ee415d 100644
--- a/ndn-cxx/encoding/buffer-stream.cpp
+++ b/ndn-cxx/encoding/buffer-stream.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2018 Regents of the University of California.
+ * Copyright (c) 2013-2022 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -24,13 +24,13 @@
 namespace ndn {
 namespace detail {
 
-BufferAppendDevice::BufferAppendDevice(Buffer& container)
+BufferSink::BufferSink(Buffer& container)
   : m_container(container)
 {
 }
 
 std::streamsize
-BufferAppendDevice::write(const char_type* s, std::streamsize n)
+BufferSink::write(const char_type* s, std::streamsize n)
 {
   m_container.insert(m_container.end(), s, s + n);
   return n;
@@ -39,21 +39,27 @@
 } // namespace detail
 
 OBufferStream::OBufferStream()
-  : m_buffer(make_shared<Buffer>())
-  , m_device(*m_buffer)
+  : m_buffer(std::make_shared<Buffer>())
 {
-  open(m_device);
+  open(detail::BufferSink{*m_buffer});
 }
 
-OBufferStream::~OBufferStream()
+OBufferStream::~OBufferStream() noexcept
 {
-  close();
+  try {
+    close();
+  }
+  catch (...) {
+    // ignore
+  }
 }
 
 shared_ptr<Buffer>
 OBufferStream::buf()
 {
-  flush();
+  if (is_open()) {
+    flush();
+  }
   return m_buffer;
 }
 
diff --git a/ndn-cxx/encoding/buffer-stream.hpp b/ndn-cxx/encoding/buffer-stream.hpp
index 6716221..16e4806 100644
--- a/ndn-cxx/encoding/buffer-stream.hpp
+++ b/ndn-cxx/encoding/buffer-stream.hpp
@@ -26,63 +26,61 @@
 
 #include "ndn-cxx/encoding/buffer.hpp"
 
-#include <boost/iostreams/categories.hpp>
+#include <boost/iostreams/concepts.hpp>
 #include <boost/iostreams/stream.hpp>
 
 namespace ndn {
-
 namespace detail {
 
-/** @brief (implementation detail) a Boost.Iostreams.Sink which appends to an \p ndn::Buffer
+/**
+ * @brief (implementation detail) A Boost.Iostreams.Sink that appends to a Buffer.
  */
-class BufferAppendDevice
+class BufferSink : public boost::iostreams::sink
 {
 public:
-  typedef char char_type;
-  typedef boost::iostreams::sink_tag category;
-
   explicit
-  BufferAppendDevice(Buffer& container);
+  BufferSink(Buffer& container);
 
   std::streamsize
   write(const char_type* s, std::streamsize n);
 
-protected:
+private:
   Buffer& m_container;
 };
 
 } // namespace detail
 
-/** @brief implements an output stream that constructs \p ndn::Buffer
+/**
+ * @brief An output stream that writes to a Buffer.
  *
- *  The benefit of using stream interface is that it provides automatic buffering of
- *  written data and eliminates (or reduces) overhead of resizing the underlying buffer
- *  when writing small pieces of data.
+ * The benefit of using stream interface is that it provides automatic buffering of
+ * written data and eliminates (or reduces) overhead of resizing the underlying buffer
+ * when writing small pieces of data.
  *
- *  Usage example:
- *  @code
- *  OBufferStream obuf;
- *  obuf.put(0);
- *  obuf.write(anotherBuffer, anotherBufferSize);
- *  shared_ptr<Buffer> buf = obuf.buf();
- *  @endcode
+ * Usage example:
+ * @code
+ * OBufferStream obuf;
+ * obuf << "foo";
+ * obuf.put(0);
+ * obuf.write(anotherBuffer, anotherBufferSize);
+ * std::shared_ptr<Buffer> buf = obuf.buf();
+ * @endcode
  */
-class OBufferStream : public boost::iostreams::stream<detail::BufferAppendDevice>
+class OBufferStream : public boost::iostreams::stream<detail::BufferSink>
 {
 public:
   OBufferStream();
 
-  ~OBufferStream() override;
+  ~OBufferStream() noexcept override;
 
   /**
-   * Flush written data to the stream and return shared pointer to the underlying buffer
+   * @brief Return a shared pointer to the underlying buffer.
    */
   shared_ptr<Buffer>
   buf();
 
 private:
-  BufferPtr m_buffer;
-  detail::BufferAppendDevice m_device;
+  shared_ptr<Buffer> m_buffer;
 };
 
 } // namespace ndn
diff --git a/tests/unit/encoding/buffer-stream.t.cpp b/tests/unit/encoding/buffer-stream.t.cpp
index 9418f8c..3f67d50 100644
--- a/tests/unit/encoding/buffer-stream.t.cpp
+++ b/tests/unit/encoding/buffer-stream.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2018 Regents of the University of California.
+ * Copyright (c) 2013-2022 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -63,11 +63,22 @@
 BOOST_AUTO_TEST_CASE(Destructor) // Bug 3727
 {
   auto os = make_unique<OBufferStream>();
+  auto buf = os->buf();
   *os << 'x';
-  os.reset(); // should not cause use-after-free
+  // do NOT flush or call buf() here
 
-  // avoid "test case [...] did not check any assertions" message from Boost.Test
-  BOOST_CHECK(true);
+  os.reset(); // should not cause use-after-free
+  BOOST_CHECK_EQUAL(buf->size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(Close) // Bug 5240
+{
+  OBufferStream os;
+  os << "foo";
+  os.close();
+
+  auto buf = os.buf(); // should not cause assertion failure
+  BOOST_CHECK_EQUAL(buf->size(), 3);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestBufferStream