Add ability to configure maximum packet size for sync data

Change-Id: Iab7913d094a1b5d64ef5cc8e00b2624993b59ab5
Refs: #4140
diff --git a/src/logic.cpp b/src/logic.cpp
index 84cf5be..5e87153 100644
--- a/src/logic.cpp
+++ b/src/logic.cpp
@@ -26,6 +26,7 @@
 #include "logic.hpp"
 #include "logger.hpp"
 
+#include <ndn-cxx/util/backports.hpp>
 #include <ndn-cxx/util/string-helper.hpp>
 
 INIT_LOGGER(Logic);
@@ -59,6 +60,44 @@
 const ndn::name::Component Logic::RESET_COMPONENT("reset");
 const ndn::name::Component Logic::RECOVERY_COMPONENT("recovery");
 
+const size_t NDNLP_EXPECTED_OVERHEAD = 20;
+
+/**
+ * Get maximum packet limit
+ *
+ * By default, it returns `ndn::MAX_NDN_PACKET_SIZE`.
+ * The returned value can be customized using the environment variable `CHRONOSYNC_MAX_PACKET_SIZE`,
+ * but the returned value will be at least 500 and no more than `ndn::MAX_NDN_PACKET_SIZE`.
+ */
+#ifndef CHRONOSYNC_HAVE_TESTS
+static
+#endif // CHRONOSYNC_HAVE_TESTS
+size_t
+getMaxPacketLimit()
+{
+  static size_t limit = 0;
+#ifndef CHRONOSYNC_HAVE_TESTS
+  if (limit != 0) {
+    return limit;
+  }
+#endif // CHRONOSYNC_HAVE_TESTS
+
+  if (getenv("CHRONOSYNC_MAX_PACKET_SIZE") != nullptr) {
+    try {
+      limit = ndn::clamp<size_t>(boost::lexical_cast<size_t>(getenv("CHRONOSYNC_MAX_PACKET_SIZE")),
+                                 500, ndn::MAX_NDN_PACKET_SIZE);
+    }
+    catch (const boost::bad_lexical_cast&) {
+      limit = ndn::MAX_NDN_PACKET_SIZE;
+    }
+  }
+  else {
+    limit = ndn::MAX_NDN_PACKET_SIZE;
+  }
+
+  return limit;
+}
+
 Logic::Logic(ndn::Face& face,
              const Name& syncPrefix,
              const Name& defaultUserPrefix,
@@ -666,9 +705,11 @@
   else
     m_keyChain.sign(syncReply, security::signingByIdentity(m_nodeList[nodePrefix].signingId));
 
-  if (syncReply.wireEncode().size() > ndn::MAX_NDN_PACKET_SIZE) {
-    _LOG_DEBUG("Sync reply size exceeded MAX_NDN_PACKET_SIZE");
-    auto maxContentSize = ndn::MAX_NDN_PACKET_SIZE - (syncReply.wireEncode().size() - state.wireEncode().size());
+  if (syncReply.wireEncode().size() > getMaxPacketLimit() - NDNLP_EXPECTED_OVERHEAD) {
+    _LOG_DEBUG("Sync reply size exceeded maximum packet limit (" << getMaxPacketLimit() << ")");
+    auto maxContentSize = getMaxPacketLimit() - (syncReply.wireEncode().size() - syncReply.getContent().size());
+    maxContentSize -= NDNLP_EXPECTED_OVERHEAD;
+
     State partialState;
     trimState(partialState, state, maxContentSize);
     syncReply.setContent(partialState.wireEncode());
diff --git a/src/logic.hpp b/src/logic.hpp
index f1f6a0e..d33ed39 100644
--- a/src/logic.hpp
+++ b/src/logic.hpp
@@ -525,6 +525,10 @@
   static int s_instanceCounter;
 };
 
+#ifdef CHRONOSYNC_HAVE_TESTS
+size_t
+getMaxPacketLimit();
+#endif // CHRONOSYNC_HAVE_TESTS
 
 } // namespace chronosync
 
diff --git a/tests/unit-tests/test-logic.cpp b/tests/unit-tests/test-logic.cpp
index dc7e2ef..fb0422a 100644
--- a/tests/unit-tests/test-logic.cpp
+++ b/tests/unit-tests/test-logic.cpp
@@ -348,6 +348,41 @@
   BOOST_REQUIRE(syncReply.wireEncode().size() < ndn::MAX_NDN_PACKET_SIZE);
 }
 
+class MaxPacketCustomizationFixture
+{
+public:
+  MaxPacketCustomizationFixture()
+  {
+    if (getenv("CHRONOSYNC_MAX_PACKET_SIZE") != nullptr) {
+      oldSize = std::string(getenv("CHRONOSYNC_MAX_PACKET_SIZE"));
+      unsetenv("CHRONOSYNC_MAX_PACKET_SIZE");
+    }
+  }
+
+  ~MaxPacketCustomizationFixture()
+  {
+    if (oldSize) {
+      setenv("CHRONOSYNC_MAX_PACKET_SIZE", oldSize->c_str(), 1);
+    }
+  }
+private:
+  ndn::optional<std::string> oldSize;
+};
+
+BOOST_FIXTURE_TEST_CASE(MaxPacketCustomization, MaxPacketCustomizationFixture)
+{
+  BOOST_CHECK_EQUAL(getMaxPacketLimit(), ndn::MAX_NDN_PACKET_SIZE);
+
+  setenv("CHRONOSYNC_MAX_PACKET_SIZE", "1500", 1);
+  BOOST_CHECK_EQUAL(getMaxPacketLimit(), 1500);
+
+  setenv("CHRONOSYNC_MAX_PACKET_SIZE", ndn::to_string(ndn::MAX_NDN_PACKET_SIZE * 100).c_str(), 1);
+  BOOST_CHECK_EQUAL(getMaxPacketLimit(), ndn::MAX_NDN_PACKET_SIZE);
+
+  setenv("CHRONOSYNC_MAX_PACKET_SIZE", "1", 1);
+  BOOST_CHECK_EQUAL(getMaxPacketLimit(), 500);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace test