transport: make error messages more informative

Change-Id: I188e1bef8ceceb6acab800e0db92f2c80abc64e5
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 267e3f4..ab194b9 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -2,7 +2,7 @@
 on:
   push:
     tags:
-      - 'ndn-cxx-*'
+      - 'ndn-cxx-[0-9]+*'
   schedule:
     # twice a month
     - cron: '20 4 5,20 * *'
diff --git a/ndn-cxx/face.cpp b/ndn-cxx/face.cpp
index bc590f0..3a6304a 100644
--- a/ndn-cxx/face.cpp
+++ b/ndn-cxx/face.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2024 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -118,11 +118,11 @@
       NDN_THROW(ConfigFile::Error("Unsupported transport protocol \"" + protocol + "\""));
     }
   }
-  catch (const Transport::Error&) {
-    NDN_THROW_NESTED(ConfigFile::Error("Failed to create transport"));
+  catch (const Transport::Error& e) {
+    NDN_THROW_NESTED(ConfigFile::Error("Failed to create transport: "s + e.what()));
   }
-  catch (const FaceUri::Error&) {
-    NDN_THROW_NESTED(ConfigFile::Error("Failed to create transport"));
+  catch (const FaceUri::Error& e) {
+    NDN_THROW_NESTED(ConfigFile::Error("Failed to create transport: "s + e.what()));
   }
 }
 
diff --git a/ndn-cxx/transport/detail/stream-transport-impl.hpp b/ndn-cxx/transport/detail/stream-transport-impl.hpp
index ff10539..0c65a5e 100644
--- a/ndn-cxx/transport/detail/stream-transport-impl.hpp
+++ b/ndn-cxx/transport/detail/stream-transport-impl.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2024 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -26,24 +26,26 @@
 
 #include <boost/asio/steady_timer.hpp>
 #include <boost/asio/write.hpp>
+#include <boost/lexical_cast.hpp>
 
 #include <list>
 #include <queue>
 
 namespace ndn::detail {
 
-/** \brief Implementation detail of a Boost.Asio-based stream-oriented transport.
- *  \tparam BaseTransport a subclass of Transport
- *  \tparam Protocol a Boost.Asio stream-oriented protocol, e.g. boost::asio::ip::tcp
- *                   or boost::asio::local::stream_protocol
+/**
+ * \brief Implementation detail of a Boost.Asio-based stream-oriented transport.
+ * \tparam BaseTransport a subclass of Transport
+ * \tparam Protocol a Boost.Asio stream-oriented protocol, e.g., `boost::asio::ip::tcp`
+ *                  or `boost::asio::local::stream_protocol`
  */
 template<typename BaseTransport, typename Protocol>
 class StreamTransportImpl : public std::enable_shared_from_this<StreamTransportImpl<BaseTransport, Protocol>>
 {
-public:
-  using Impl = StreamTransportImpl<BaseTransport, Protocol>;
+protected:
   using TransmissionQueue = std::queue<Block, std::list<Block>>;
 
+public:
   StreamTransportImpl(BaseTransport& transport, boost::asio::io_context& ioCtx)
     : m_transport(transport)
     , m_socket(ioCtx)
@@ -57,18 +59,25 @@
     if (m_transport.getState() == Transport::State::CONNECTING) {
       return;
     }
+
+    m_endpoint = endpoint;
     m_transport.setState(Transport::State::CONNECTING);
 
     // Wait at most 4 seconds to connect
     /// @todo Decide whether this number should be configurable
     m_connectTimer.expires_after(std::chrono::seconds(4));
-    m_connectTimer.async_wait([self = this->shared_from_this()] (const auto& error) {
-      self->connectTimeoutHandler(error);
+    m_connectTimer.async_wait([self = this->shared_from_this()] (const auto& ec) {
+      if (ec) // e.g., cancelled timer
+        return;
+
+      self->m_transport.close();
+      NDN_THROW(Transport::Error(boost::system::errc::make_error_code(boost::system::errc::timed_out),
+                                 "could not connect to NDN forwarder at " +
+                                 boost::lexical_cast<std::string>(self->m_endpoint)));
     });
 
-    m_socket.open();
-    m_socket.async_connect(endpoint, [self = this->shared_from_this()] (const auto& error) {
-      self->connectHandler(error);
+    m_socket.async_connect(m_endpoint, [self = this->shared_from_this()] (const auto& ec) {
+      self->connectHandler(ec);
     });
   }
 
@@ -125,8 +134,13 @@
     m_connectTimer.cancel();
 
     if (error) {
+      if (error == boost::asio::error::operation_aborted) {
+        // async_connect was explicitly cancelled (e.g., socket close)
+        return;
+      }
       m_transport.close();
-      NDN_THROW(Transport::Error(error, "error while connecting to the forwarder"));
+      NDN_THROW(Transport::Error(error, "could not connect to NDN forwarder at " +
+                                 boost::lexical_cast<std::string>(m_endpoint)));
     }
 
     m_transport.setState(Transport::State::PAUSED);
@@ -138,16 +152,6 @@
   }
 
   void
-  connectTimeoutHandler(const boost::system::error_code& error)
-  {
-    if (error) // e.g., cancelled timer
-      return;
-
-    m_transport.close();
-    NDN_THROW(Transport::Error(error, "error while connecting to the forwarder"));
-  }
-
-  void
   asyncWrite()
   {
     BOOST_ASSERT(!m_transmissionQueue.empty());
@@ -155,12 +159,12 @@
       // capture a copy of the shared_ptr to "this" to prevent deallocation
       [this, self = this->shared_from_this()] (const auto& error, size_t) {
         if (error) {
-          if (error == boost::system::errc::operation_canceled) {
-            // async receive has been explicitly cancelled (e.g., socket close)
+          if (error == boost::asio::error::operation_aborted) {
+            // async_write was explicitly cancelled (e.g., socket close)
             return;
           }
           m_transport.close();
-          NDN_THROW(Transport::Error(error, "error while writing data to socket"));
+          NDN_THROW(Transport::Error(error, "socket write error"));
         }
 
         if (m_transport.getState() == Transport::State::CLOSED) {
@@ -184,12 +188,12 @@
       // capture a copy of the shared_ptr to "this" to prevent deallocation
       [this, self = this->shared_from_this()] (const auto& error, size_t nBytesRecvd) {
         if (error) {
-          if (error == boost::system::errc::operation_canceled) {
-            // async receive has been explicitly cancelled (e.g., socket close)
+          if (error == boost::asio::error::operation_aborted) {
+            // async_receive was explicitly cancelled (e.g., socket close)
             return;
           }
           m_transport.close();
-          NDN_THROW(Transport::Error(error, "error while receiving data from socket"));
+          NDN_THROW(Transport::Error(error, "socket read error"));
         }
 
         m_inputBufferSize += nBytesRecvd;
@@ -199,7 +203,7 @@
         bool hasProcessedSome = processAllReceived(m_inputBuffer, offset, m_inputBufferSize);
         if (!hasProcessedSome && m_inputBufferSize == MAX_NDN_PACKET_SIZE && offset == 0) {
           m_transport.close();
-          NDN_THROW(Transport::Error("input buffer full, but a valid TLV cannot be decoded"));
+          NDN_THROW(Transport::Error("receive buffer full, but a valid TLV cannot be decoded"));
         }
 
         if (offset > 0) {
@@ -232,12 +236,12 @@
 
 protected:
   BaseTransport& m_transport;
-
+  typename Protocol::endpoint m_endpoint;
   typename Protocol::socket m_socket;
-  uint8_t m_inputBuffer[MAX_NDN_PACKET_SIZE];
-  size_t m_inputBufferSize = 0;
-  TransmissionQueue m_transmissionQueue;
   boost::asio::steady_timer m_connectTimer;
+  TransmissionQueue m_transmissionQueue;
+  size_t m_inputBufferSize = 0;
+  uint8_t m_inputBuffer[MAX_NDN_PACKET_SIZE];
 };
 
 } // namespace ndn::detail
diff --git a/ndn-cxx/transport/detail/stream-transport-with-resolver-impl.hpp b/ndn-cxx/transport/detail/stream-transport-with-resolver-impl.hpp
index dec886a..b342425 100644
--- a/ndn-cxx/transport/detail/stream-transport-with-resolver-impl.hpp
+++ b/ndn-cxx/transport/detail/stream-transport-with-resolver-impl.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2024 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -26,8 +26,9 @@
 
 namespace ndn::detail {
 
-/** \brief Implementation detail of a Boost.Asio-based stream-oriented transport
- *         with resolver support.
+/**
+ * \brief Implementation detail of a Boost.Asio-based stream-oriented transport
+ *        with resolver support.
  */
 template<typename BaseTransport, typename Protocol>
 class StreamTransportWithResolverImpl : public StreamTransportImpl<BaseTransport, Protocol>
@@ -44,38 +45,47 @@
     if (this->m_transport.getState() == Transport::State::CONNECTING) {
       return;
     }
+
     this->m_transport.setState(Transport::State::CONNECTING);
+    auto hostAndPort = std::string(host) + ':' + std::string(port);
 
     // Wait at most 4 seconds to connect
     /// @todo Decide whether this number should be configurable
     this->m_connectTimer.expires_after(std::chrono::seconds(4));
-    this->m_connectTimer.async_wait([self = this->shared_from_base()] (const auto& ec) {
-      self->connectTimeoutHandler(ec);
+    this->m_connectTimer.async_wait([self = this->shared_from_base(), hostAndPort] (const auto& ec) {
+      if (ec) // e.g., cancelled timer
+        return;
+
+      self->m_transport.close();
+      NDN_THROW(Transport::Error(boost::system::errc::make_error_code(boost::system::errc::timed_out),
+                                 "could not connect to NDN forwarder at " + hostAndPort));
     });
 
     auto resolver = make_shared<typename Protocol::resolver>(this->m_socket.get_executor());
-    resolver->async_resolve(host, port, [self = this->shared_from_base(), resolver] (auto&&... args) {
-      self->resolveHandler(std::forward<decltype(args)>(args)..., resolver);
-    });
+    resolver->async_resolve(host, port,
+      [self = this->shared_from_base(), hostAndPort, resolver] (auto&&... args) {
+        self->resolveHandler(hostAndPort, std::forward<decltype(args)>(args)...);
+      });
   }
 
 protected:
   void
-  resolveHandler(const boost::system::error_code& error,
-                 const typename Protocol::resolver::results_type& endpoints,
-                 const shared_ptr<typename Protocol::resolver>&)
+  resolveHandler(const std::string& hostAndPort,
+                 const boost::system::error_code& error,
+                 const typename Protocol::resolver::results_type& endpoints)
   {
     if (error) {
-      if (error == boost::system::errc::operation_canceled)
+      if (error == boost::asio::error::operation_aborted)
         return;
 
       this->m_transport.close();
-      NDN_THROW(Transport::Error(error, "unable to resolve host or port"));
+      NDN_THROW(Transport::Error(error, "could not resolve " + hostAndPort));
     }
 
     BOOST_ASSERT(!endpoints.empty()); // guaranteed by Asio if the resolve operation is successful
 
-    this->m_socket.async_connect(*endpoints.begin(), [self = this->shared_from_base()] (const auto& ec) {
+    this->m_endpoint = *endpoints.begin();
+    this->m_socket.async_connect(this->m_endpoint, [self = this->shared_from_base()] (const auto& ec) {
       self->connectHandler(ec);
     });
   }
diff --git a/ndn-cxx/transport/transport.cpp b/ndn-cxx/transport/transport.cpp
index 1b7c774..11a3246 100644
--- a/ndn-cxx/transport/transport.cpp
+++ b/ndn-cxx/transport/transport.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2023 Regents of the University of California.
+ * Copyright (c) 2013-2024 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -23,8 +23,8 @@
 
 namespace ndn {
 
-Transport::Error::Error(const boost::system::error_code& code, const std::string& msg)
-  : std::runtime_error(msg + (code.value() ? " (" + code.message() + ")" : ""))
+Transport::Error::Error(const boost::system::error_code& ec, const std::string& msg)
+  : std::runtime_error(msg + (ec ? " (" + ec.message() + ")" : ""))
 {
 }