repo: Watching prefix

Change-Id: Ia315d6e529fa4402ad9ca4706b70db1e75eb054c
Refs: #1784
diff --git a/src/handles/base-handle.hpp b/src/handles/base-handle.hpp
index 9427356..44ac4a8 100644
--- a/src/handles/base-handle.hpp
+++ b/src/handles/base-handle.hpp
@@ -110,10 +110,10 @@
 inline void
 BaseHandle::reply(const Interest& commandInterest, const RepoCommandResponse& response)
 {
-  Data rdata(commandInterest.getName());
-  rdata.setContent(response.wireEncode());
-  m_keyChain.sign(rdata);
-  m_face.put(rdata);
+  shared_ptr<Data> rdata = make_shared<Data>(commandInterest.getName());
+  rdata->setContent(response.wireEncode());
+  m_keyChain.sign(*rdata);
+  m_face.put(*rdata);
 }
 
 inline void
diff --git a/src/handles/watch-handle.cpp b/src/handles/watch-handle.cpp
new file mode 100644
index 0000000..15a82fa
--- /dev/null
+++ b/src/handles/watch-handle.cpp
@@ -0,0 +1,379 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  Regents of the University of California.
+ *
+ * This file is part of NDN repo-ng (Next generation of NDN repository).
+ * See AUTHORS.md for complete list of repo-ng authors and contributors.
+ *
+ * repo-ng is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * repo-ng 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * repo-ng, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "watch-handle.hpp"
+
+namespace repo {
+
+static const milliseconds PROCESS_DELETE_TIME(10000);
+static const milliseconds DEFAULT_INTEREST_LIFETIME(4000);
+
+WatchHandle::WatchHandle(Face& face, RepoStorage& storageHandle, KeyChain& keyChain,
+                         Scheduler& scheduler, ValidatorConfig& validator)
+  : BaseHandle(face, storageHandle, keyChain, scheduler)
+  , m_validator(validator)
+  , m_interestNum(0)
+  , m_maxInterestNum(0)
+  , m_interestLifetime(DEFAULT_INTEREST_LIFETIME)
+  , m_watchTimeout(0)
+  , m_startTime(steady_clock::now())
+  , m_size(0)
+{
+}
+
+void
+WatchHandle::deleteProcess(const Name& name)
+{
+  m_processes.erase(name);
+}
+
+// Interest.
+void
+WatchHandle::onInterest(const Name& prefix, const Interest& interest)
+{
+  m_validator.validate(interest,
+                       bind(&WatchHandle::onValidated, this, _1, prefix),
+                       bind(&WatchHandle::onValidationFailed, this, _1, _2));
+}
+
+void
+WatchHandle::onRegistered(const Name& prefix)
+{
+  getFace().setInterestFilter(Name().append(prefix).append("start"),
+                              bind(&WatchHandle::onInterest, this, _1, _2));
+  getFace().setInterestFilter(Name().append(prefix).append("check"),
+                              bind(&WatchHandle::onCheckInterest, this, _1, _2));
+  getFace().setInterestFilter(Name().append(prefix).append("stop"),
+                              bind(&WatchHandle::onStopInterest, this, _1, _2));
+}
+
+// onRegisterFailed for watch start.
+void
+WatchHandle::onRegisterFailed(const Name& prefix, const std::string& reason)
+{
+  std::cerr << reason << std::endl;
+  throw Error("watch prefix registration failed");
+}
+
+void
+WatchHandle::onValidated(const shared_ptr<const Interest>& interest, const Name& prefix)
+{
+  RepoCommandParameter parameter;
+  try {
+    extractParameter(*interest, prefix, parameter);
+  }
+  catch (RepoCommandParameter::Error) {
+    negativeReply(*interest, 403);
+    return;
+  }
+
+  processWatchCommand(*interest, parameter);
+}
+
+void WatchHandle::watchStop(const Name& name)
+{
+  m_processes[name].second = false;
+  m_maxInterestNum = 0;
+  m_interestNum = 0;
+  m_startTime = steady_clock::now();
+  m_watchTimeout = milliseconds(0);
+  m_interestLifetime = DEFAULT_INTEREST_LIFETIME;
+  m_size = 0;
+}
+
+void
+WatchHandle::onValidationFailed(const shared_ptr<const Interest>& interest, const string& reason)
+{
+  std::cerr << reason << std::endl;
+  negativeReply(*interest, 401);
+}
+
+void
+WatchHandle::onData(const Interest& interest, ndn::Data& data, const Name& name)
+{
+  m_validator.validate(data,
+                       bind(&WatchHandle::onDataValidated, this, interest, _1, name),
+                       bind(&WatchHandle::onDataValidationFailed, this, interest, _1, _2, name));
+}
+
+void
+WatchHandle::onDataValidated(const Interest& interest, const shared_ptr<const Data>& data,
+                             const Name& name)
+{
+  if (!m_processes[name].second) {
+    return;
+  }
+  if (getStorageHandle().insertData(*data)) {
+    m_size++;
+    if (!onRunning(name))
+      return;
+
+    Interest fetchInterest(interest.getName());
+    fetchInterest.setSelectors(interest.getSelectors());
+    fetchInterest.setInterestLifetime(m_interestLifetime);
+    fetchInterest.setChildSelector(1);
+
+    // update selectors
+    // if data name is equal to interest name, use MinSuffixComponents selecor to exclude this data
+    if (data->getName().size() == interest.getName().size()) {
+      fetchInterest.setMinSuffixComponents(2);
+    }
+    else {
+      Exclude exclude;
+      if (!interest.getExclude().empty()) {
+        exclude = interest.getExclude();
+      }
+
+      exclude.excludeBefore(data->getName()[interest.getName().size()]);
+      fetchInterest.setExclude(exclude);
+    }
+
+    ++m_interestNum;
+    getFace().expressInterest(fetchInterest,
+                              bind(&WatchHandle::onData, this, _1, _2, name),
+                              bind(&WatchHandle::onTimeout, this, _1, name));
+  }
+  else {
+    throw Error("Insert into Repo Failed");
+  }
+  m_processes[name].first.setInsertNum(m_size);
+}
+
+void
+WatchHandle::onDataValidationFailed(const Interest& interest, const shared_ptr<const Data>& data,
+                                    const std::string& reason, const Name& name)
+{
+  std::cerr << reason << std::endl;
+  if (!m_processes[name].second) {
+    return;
+  }
+  if (!onRunning(name))
+    return;
+
+  Interest fetchInterest(interest.getName());
+  fetchInterest.setSelectors(interest.getSelectors());
+  fetchInterest.setInterestLifetime(m_interestLifetime);
+  fetchInterest.setChildSelector(1);
+
+  // update selectors
+  // if data name is equal to interest name, use MinSuffixComponents selecor to exclude this data
+  if (data->getName().size() == interest.getName().size()) {
+    fetchInterest.setMinSuffixComponents(2);
+  }
+  else {
+    Exclude exclude;
+    if (!interest.getExclude().empty()) {
+      exclude = interest.getExclude();
+    }
+    // Only exclude this data since other data whose names are smaller may be validated and satisfied
+    exclude.excludeBefore(data->getName()[interest.getName().size()]);
+    fetchInterest.setExclude(exclude);
+  }
+
+  ++m_interestNum;
+  getFace().expressInterest(fetchInterest,
+                            bind(&WatchHandle::onData, this, _1, _2, name),
+                            bind(&WatchHandle::onTimeout, this, _1, name));
+}
+
+void
+WatchHandle::onTimeout(const ndn::Interest& interest, const Name& name)
+{
+  std::cerr << "Timeout" << std::endl;
+  if (!m_processes[name].second) {
+    return;
+  }
+  if (!onRunning(name))
+    return;
+  // selectors do not need to be updated
+  Interest fetchInterest(interest.getName());
+  fetchInterest.setSelectors(interest.getSelectors());
+  fetchInterest.setInterestLifetime(m_interestLifetime);
+  fetchInterest.setChildSelector(1);
+
+  ++m_interestNum;
+  getFace().expressInterest(fetchInterest,
+                            bind(&WatchHandle::onData, this, _1, _2, name),
+                            bind(&WatchHandle::onTimeout, this, _1, name));
+
+}
+
+void
+WatchHandle::listen(const Name& prefix)
+{
+  Name baseWatchPrefix(prefix);
+  baseWatchPrefix.append("watch");
+  getFace().registerPrefix(baseWatchPrefix,
+                           bind(&WatchHandle::onRegistered, this, _1),
+                           bind(&WatchHandle::onRegisterFailed, this, _1, _2));
+}
+
+void
+WatchHandle::onStopInterest(const Name& prefix, const Interest& interest)
+{
+  m_validator.validate(interest,
+                       bind(&WatchHandle::onStopValidated, this, _1, prefix),
+                       bind(&WatchHandle::onStopValidationFailed, this, _1, _2));
+}
+
+void
+WatchHandle::onStopValidated(const shared_ptr<const Interest>& interest, const Name& prefix)
+{
+  RepoCommandParameter parameter;
+  try {
+    extractParameter(*interest, prefix, parameter);
+  }
+  catch (RepoCommandParameter::Error) {
+    negativeReply(*interest, 403);
+    return;
+  }
+
+  watchStop(parameter.getName());
+  negativeReply(*interest, 101);
+}
+
+void
+WatchHandle::onStopValidationFailed(const shared_ptr<const Interest>& interest,
+                                    const std::string& reason)
+{
+  std::cerr << reason << std::endl;
+  negativeReply(*interest, 401);
+}
+
+void
+WatchHandle::onCheckInterest(const Name& prefix, const Interest& interest)
+{
+  m_validator.validate(interest,
+                       bind(&WatchHandle::onCheckValidated, this, _1, prefix),
+                       bind(&WatchHandle::onCheckValidationFailed, this, _1, _2));
+}
+
+void
+WatchHandle::onCheckValidated(const shared_ptr<const Interest>& interest, const Name& prefix)
+{
+  RepoCommandParameter parameter;
+  try {
+    extractParameter(*interest, prefix, parameter);
+  }
+  catch (RepoCommandParameter::Error) {
+    negativeReply(*interest, 403);
+    return;
+  }
+
+  if (!parameter.hasName()) {
+    negativeReply(*interest, 403);
+    return;
+  }
+  //check whether this process exists
+  Name name = parameter.getName();
+  if (m_processes.count(name) == 0) {
+    std::cerr << "no such process name: " << name << std::endl;
+    negativeReply(*interest, 404);
+    return;
+  }
+
+  RepoCommandResponse& response = m_processes[name].first;
+    if (!m_processes[name].second) {
+    response.setStatusCode(101);
+  }
+
+  reply(*interest, response);
+
+}
+
+void
+WatchHandle::onCheckValidationFailed(const shared_ptr<const Interest>& interest,
+                                     const std::string& reason)
+{
+  std::cerr << reason << std::endl;
+  negativeReply(*interest, 401);
+}
+
+void
+WatchHandle::deferredDeleteProcess(const Name& name)
+{
+  getScheduler().scheduleEvent(PROCESS_DELETE_TIME,
+                               ndn::bind(&WatchHandle::deleteProcess, this, name));
+}
+
+void
+WatchHandle::processWatchCommand(const Interest& interest,
+                                 RepoCommandParameter& parameter)
+{
+  // if there is no watchTimeout specified, m_watchTimeout will be set as 0 and this handle will run forever
+  if (parameter.hasWatchTimeout()) {
+    m_watchTimeout = parameter.getWatchTimeout();
+  }
+  else {
+    m_watchTimeout = milliseconds(0);
+  }
+
+  // if there is no maxInterestNum specified, m_maxInterestNum will be 0, which means infinity
+  if (parameter.hasMaxInterestNum()) {
+    m_maxInterestNum = parameter.getMaxInterestNum();
+  }
+  else {
+    m_maxInterestNum = 0;
+  }
+
+  if (parameter.hasInterestLifetime()) {
+    m_interestLifetime = parameter.getInterestLifetime();
+  }
+
+  reply(interest, RepoCommandResponse().setStatusCode(100));
+
+  m_processes[parameter.getName()] =
+                std::make_pair(RepoCommandResponse().setStatusCode(300), true);
+  Interest fetchInterest(parameter.getName());
+  if (parameter.hasSelectors()) {
+    fetchInterest.setSelectors(parameter.getSelectors());
+  }
+  fetchInterest.setChildSelector(1);
+  fetchInterest.setInterestLifetime(m_interestLifetime);
+  m_startTime = steady_clock::now();
+  m_interestNum++;
+  getFace().expressInterest(fetchInterest,
+                            bind(&WatchHandle::onData, this, _1, _2, parameter.getName()),
+                            bind(&WatchHandle::onTimeout, this, _1, parameter.getName()));
+}
+
+
+void
+WatchHandle::negativeReply(const Interest& interest, int statusCode)
+{
+  RepoCommandResponse response;
+  response.setStatusCode(statusCode);
+  reply(interest, response);
+}
+
+bool
+WatchHandle::onRunning(const Name& name)
+{
+  bool isTimeout = (m_watchTimeout != milliseconds::zero() &&
+                    steady_clock::now() - m_startTime > m_watchTimeout);
+  bool isMaxInterest = m_interestNum >= m_maxInterestNum && m_maxInterestNum != 0;
+  if (isTimeout || isMaxInterest) {
+    deferredDeleteProcess(name);
+    watchStop(name);
+    return false;
+  }
+  return true;
+}
+
+} //namespace repo
diff --git a/src/handles/watch-handle.hpp b/src/handles/watch-handle.hpp
new file mode 100644
index 0000000..10cddda
--- /dev/null
+++ b/src/handles/watch-handle.hpp
@@ -0,0 +1,170 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  Regents of the University of California.
+ *
+ * This file is part of NDN repo-ng (Next generation of NDN repository).
+ * See AUTHORS.md for complete list of repo-ng authors and contributors.
+ *
+ * repo-ng is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * repo-ng 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * repo-ng, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef REPO_HANDLES_WATCH_HANDLE_HPP
+#define REPO_HANDLES_WATCH_HANDLE_HPP
+
+#include "base-handle.hpp"
+
+#include <queue>
+
+namespace repo {
+
+using std::map;
+using std::pair;
+using std::queue;
+using namespace ndn::time;
+/**
+ * @brief WatchHandle provides a different way for repo to insert data.
+ *
+ * Repo keeps sending interest to request the data with same prefix,
+ *
+ * but with different exclude selectors(updated every time). Repo will stop
+ *
+ * watching the prefix until a command interest tell it to stop, the total
+ *
+ * amount of sent interests reaches a specific number or time out.
+ */
+class WatchHandle : public BaseHandle
+{
+
+public:
+  class Error : public BaseHandle::Error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : BaseHandle::Error(what)
+    {
+    }
+  };
+
+
+public:
+  WatchHandle(Face& face, RepoStorage& storageHandle, KeyChain& keyChain,
+              Scheduler& scheduler, ValidatorConfig& validator);
+
+  virtual void
+  listen(const Name& prefix);
+
+private: // watch-insert command
+  /**
+   * @brief handle watch commands
+   */
+  void
+  onInterest(const Name& prefix, const Interest& interest);
+
+  void
+  onValidated(const shared_ptr<const Interest>& interest, const Name& prefix);
+
+  void
+  onValidationFailed(const shared_ptr<const Interest>& interest, const string& reason);
+
+  void
+  onRegistered(const Name& prefix);
+
+  void
+  onRegisterFailed(const Name& prefix, const std::string& reason);
+
+private: // data fetching
+  /**
+   * @brief fetch data and send next interest
+   */
+  void
+  onData(const Interest& interest, Data& data, const Name& name);
+
+  /**
+   * @brief handle when fetching one data timeout
+   */
+  void
+  onTimeout(const Interest& interest, const Name& name);
+
+  void
+  onDataValidated(const Interest& interest, const shared_ptr<const Data>& data,
+                  const Name& name);
+
+  /**
+   * @brief failure of validation
+   */
+  void
+  onDataValidationFailed(const Interest& interest, const shared_ptr<const Data>& data,
+                         const std::string& reason, const Name& name);
+
+
+  void
+  processWatchCommand(const Interest& interest, RepoCommandParameter& parameter);
+
+  void
+  watchStop(const Name& name);
+
+private: // watch state check command
+  /**
+   * @brief handle watch check command
+   */
+  void
+  onCheckInterest(const Name& prefix, const Interest& interest);
+
+  void
+  onCheckValidated(const shared_ptr<const Interest>& interest, const Name& prefix);
+
+  void
+  onCheckValidationFailed(const shared_ptr<const Interest>& interest, const std::string& reason);
+
+private: // watch stop command
+  /**
+   * @brief handle watch stop command
+   */
+  void
+  onStopInterest(const Name& prefix, const Interest& interest);
+
+  void
+  onStopValidated(const shared_ptr<const Interest>& interest, const Name& prefix);
+
+  void
+  onStopValidationFailed(const shared_ptr<const Interest>& interest, const std::string& reason);
+
+private:
+  void
+  negativeReply(const Interest& interest, int statusCode);
+
+  void
+  deferredDeleteProcess(const Name& name);
+
+  void
+  deleteProcess(const Name& name);
+
+  bool
+  onRunning(const Name& name);
+
+private:
+
+  ValidatorConfig& m_validator;
+
+  map<Name, std::pair<RepoCommandResponse, bool> > m_processes;
+  int64_t m_interestNum;
+  int64_t m_maxInterestNum;
+  milliseconds m_interestLifetime;
+  milliseconds m_watchTimeout;
+  ndn::time::steady_clock::TimePoint m_startTime;
+  int64_t m_size;
+};
+
+} // namespace repo
+
+#endif // REPO_HANDLES_Watch_HANDLE_HPP
diff --git a/src/handles/write-handle.cpp b/src/handles/write-handle.cpp
index 65098bf..0bc4e12 100644
--- a/src/handles/write-handle.cpp
+++ b/src/handles/write-handle.cpp
@@ -20,11 +20,13 @@
 #include "write-handle.hpp"
 
 namespace repo {
+using namespace ndn::time;
 
 static const int RETRY_TIMEOUT = 3;
 static const int DEFAULT_CREDIT = 12;
-static const ndn::time::milliseconds NOEND_TIMEOUT(10000);
-static const ndn::time::milliseconds PROCESS_DELETE_TIME(10000);
+static const milliseconds NOEND_TIMEOUT(10000);
+static const milliseconds PROCESS_DELETE_TIME(10000);
+static const milliseconds DEFAULT_INTEREST_LIFETIME(4000);
 
 WriteHandle::WriteHandle(Face& face, RepoStorage& storageHandle, KeyChain& keyChain,
                          Scheduler& scheduler,// RepoStorage& storeindex,
@@ -34,6 +36,7 @@
   , m_retryTime(RETRY_TIMEOUT)
   , m_credit(DEFAULT_CREDIT)
   , m_noEndTimeout(NOEND_TIMEOUT)
+  , m_interestLifetime(DEFAULT_INTEREST_LIFETIME)
 {
 }
 
@@ -91,7 +94,8 @@
   else {
     processSingleInsertCommand(*interest, parameter);
   }
-
+  if (parameter.hasInterestLifetime())
+    m_interestLifetime = parameter.getInterestLifetime();
 }
 
 void
@@ -233,10 +237,12 @@
   }
   process.credit = initialCredit;
   SegmentNo segment = startBlockId;
+
   for (; segment < startBlockId + initialCredit; ++segment) {
     Name fetchName = name;
     fetchName.appendSegment(segment);
     Interest interest(fetchName);
+    interest.setInterestLifetime(m_interestLifetime);
     getFace().expressInterest(interest,
                               bind(&WriteHandle::onSegmentData, this, _1, _2, processId),
                               bind(&WriteHandle::onSegmentTimeout, this, _1, processId));
@@ -330,6 +336,7 @@
   Name fetchName(interest.getName().getPrefix(-1));
   fetchName.appendSegment(sendingSegment);
   Interest fetchInterest(fetchName);
+  fetchInterest.setInterestLifetime(m_interestLifetime);
   getFace().expressInterest(fetchInterest,
                             bind(&WriteHandle::onSegmentData, this, _1, _2, processId),
                             bind(&WriteHandle::onSegmentTimeout, this, _1, processId));
@@ -380,6 +387,7 @@
     //Reput it in the queue, retryTime++
     retryTime++;
     Interest retryInterest(interest.getName());
+    retryInterest.setInterestLifetime(m_interestLifetime);
     getFace().expressInterest(retryInterest,
                               bind(&WriteHandle::onSegmentData, this, _1, _2, processId),
                               bind(&WriteHandle::onSegmentTimeout, this, _1, processId));
@@ -475,6 +483,7 @@
   response.setStatusCode(300);
 
   Interest fetchInterest(parameter.getName());
+  fetchInterest.setInterestLifetime(m_interestLifetime);
   if (parameter.hasSelectors()) {
     fetchInterest.setSelectors(parameter.getSelectors());
   }
diff --git a/src/handles/write-handle.hpp b/src/handles/write-handle.hpp
index 48bd0e4..bbbfe28 100644
--- a/src/handles/write-handle.hpp
+++ b/src/handles/write-handle.hpp
@@ -235,6 +235,7 @@
   int m_retryTime;
   int m_credit;
   ndn::time::milliseconds m_noEndTimeout;
+  ndn::time::milliseconds m_interestLifetime;
 };
 
 } // namespace repo
diff --git a/src/repo-command-parameter.hpp b/src/repo-command-parameter.hpp
index 8a8c4bb..3c4cd54 100644
--- a/src/repo-command-parameter.hpp
+++ b/src/repo-command-parameter.hpp
@@ -33,6 +33,7 @@
 using ndn::Selectors;
 using ndn::EncodingEstimator;
 using ndn::EncodingBuffer;
+using namespace ndn::time;
 
 /**
 * @brief Class defining abstraction of parameter of command for NDN Repo Protocol
@@ -57,6 +58,9 @@
     , m_hasStartBlockId(false)
     , m_hasEndBlockId(false)
     , m_hasProcessId(false)
+    , m_hasMaxInterestNum(false)
+    , m_hasWatchTimeout(false)
+    , m_hasInterestLifetime(false)
   {
   }
 
@@ -158,12 +162,6 @@
     return m_processId;
   }
 
-  bool
-  hasProcessId() const
-  {
-    return m_hasProcessId;
-  }
-
   RepoCommandParameter&
   setProcessId(uint64_t processId)
   {
@@ -173,6 +171,78 @@
     return *this;
   }
 
+  bool
+  hasProcessId() const
+  {
+    return m_hasProcessId;
+  }
+
+  uint64_t
+  getMaxInterestNum() const
+  {
+    assert(hasMaxInterestNum());
+    return m_maxInterestNum;
+  }
+
+  RepoCommandParameter&
+  setMaxInterestNum(uint64_t maxInterestNum)
+  {
+    m_maxInterestNum = maxInterestNum;
+    m_hasMaxInterestNum = true;
+    m_wire.reset();
+    return *this;
+  }
+
+  bool
+  hasMaxInterestNum() const
+  {
+    return m_hasMaxInterestNum;
+  }
+
+  milliseconds
+  getWatchTimeout() const
+  {
+    assert(hasWatchTimeout());
+    return m_watchTimeout;
+  }
+
+  RepoCommandParameter&
+  setWatchTimeout(milliseconds watchTimeout)
+  {
+    m_watchTimeout = watchTimeout;
+    m_hasWatchTimeout = true;
+    m_wire.reset();
+    return *this;
+  }
+
+  bool
+  hasWatchTimeout() const
+  {
+    return m_hasWatchTimeout;
+  }
+
+  milliseconds
+  getInterestLifetime() const
+  {
+    assert(hasInterestLifetime());
+    return m_interestLifetime;
+  }
+
+  RepoCommandParameter&
+  setInterestLifetime(milliseconds interestLifetime)
+  {
+    m_interestLifetime = interestLifetime;
+    m_hasInterestLifetime = true;
+    m_wire.reset();
+    return *this;
+  }
+
+  bool
+  hasInterestLifetime() const
+  {
+    return m_hasInterestLifetime;
+  }
+
   template<bool T>
   size_t
   wireEncode(EncodingImpl<T>& block) const;
@@ -190,11 +260,17 @@
   uint64_t m_startBlockId;
   uint64_t m_endBlockId;
   uint64_t m_processId;
+  uint64_t m_maxInterestNum;
+  milliseconds m_watchTimeout;
+  milliseconds m_interestLifetime;
 
   bool m_hasName;
   bool m_hasStartBlockId;
   bool m_hasEndBlockId;
   bool m_hasProcessId;
+  bool m_hasMaxInterestNum;
+  bool m_hasWatchTimeout;
+  bool m_hasInterestLifetime;
 
   mutable Block m_wire;
 };
@@ -227,6 +303,27 @@
     totalLength += encoder.prependVarNumber(tlv::StartBlockId);
   }
 
+  if (m_hasMaxInterestNum) {
+    variableLength = encoder.prependNonNegativeInteger(m_maxInterestNum);
+    totalLength += variableLength;
+    totalLength += encoder.prependVarNumber(variableLength);
+    totalLength += encoder.prependVarNumber(tlv::MaxInterestNum);
+  }
+
+  if (m_hasWatchTimeout) {
+    variableLength = encoder.prependNonNegativeInteger(m_watchTimeout.count());
+    totalLength += variableLength;
+    totalLength += encoder.prependVarNumber(variableLength);
+    totalLength += encoder.prependVarNumber(tlv::WatchTimeout);
+  }
+
+  if (m_hasInterestLifetime) {
+    variableLength = encoder.prependNonNegativeInteger(m_interestLifetime.count());
+    totalLength += variableLength;
+    totalLength += encoder.prependVarNumber(variableLength);
+    totalLength += encoder.prependVarNumber(tlv::InterestLifetime);
+  }
+
   if (!getSelectors().empty()) {
     totalLength += getSelectors().wireEncode(encoder);
   }
@@ -263,6 +360,9 @@
   m_hasStartBlockId = false;
   m_hasEndBlockId = false;
   m_hasProcessId = false;
+  m_hasMaxInterestNum = false;
+  m_hasWatchTimeout = false;
+  m_hasInterestLifetime = false;
 
   m_wire = wire;
 
@@ -312,6 +412,31 @@
     m_processId = readNonNegativeInteger(*val);
   }
 
+  // MaxInterestNum
+  val = m_wire.find(tlv::MaxInterestNum);
+  if (val != m_wire.elements_end())
+  {
+    m_hasMaxInterestNum = true;
+    m_maxInterestNum = readNonNegativeInteger(*val);
+  }
+
+  // WatchTimeout
+  val = m_wire.find(tlv::WatchTimeout);
+  if (val != m_wire.elements_end())
+  {
+    m_hasWatchTimeout = true;
+    m_watchTimeout = milliseconds(readNonNegativeInteger(*val));
+  }
+
+  // InterestLiftTime
+  val = m_wire.find(tlv::InterestLifetime);
+  if (val != m_wire.elements_end())
+  {
+    m_hasInterestLifetime = true;
+    m_interestLifetime = milliseconds(readNonNegativeInteger(*val));
+  }
+
+
 }
 
 inline std::ostream&
@@ -331,10 +456,22 @@
   if (repoCommandParameter.hasEndBlockId()) {
     os << " EndBlockId: " << repoCommandParameter.getEndBlockId();
   }
-  //ProcessId
+  // ProcessId
   if (repoCommandParameter.hasProcessId()) {
     os << " ProcessId: " << repoCommandParameter.getProcessId();
   }
+  // MaxInterestNum
+  if (repoCommandParameter.hasMaxInterestNum()) {
+    os << " MaxInterestNum: " << repoCommandParameter.getMaxInterestNum();
+  }
+  // WatchTimeout
+  if (repoCommandParameter.hasProcessId()) {
+    os << " WatchTimeout: " << repoCommandParameter.getWatchTimeout();
+  }
+  // InterestLifetime
+  if (repoCommandParameter.hasProcessId()) {
+    os << " InterestLifetime: " << repoCommandParameter.getInterestLifetime();
+  }
   os << " )";
   return os;
 }
diff --git a/src/repo-tlv.hpp b/src/repo-tlv.hpp
index 7d917e1..bdc3310 100644
--- a/src/repo-tlv.hpp
+++ b/src/repo-tlv.hpp
@@ -35,7 +35,9 @@
   RepoCommandResponse  = 207,
   StatusCode           = 208,
   InsertNum            = 209,
-  DeleteNum            = 210
+  DeleteNum            = 210,
+  MaxInterestNum       = 211,
+  WatchTimeout         = 212
 };
 
 } // tlv
diff --git a/src/repo.cpp b/src/repo.cpp
index a75bdd5..346fa23 100644
--- a/src/repo.cpp
+++ b/src/repo.cpp
@@ -119,6 +119,7 @@
   , m_validator(m_face)
   , m_readHandle(m_face, m_storageHandle, m_keyChain, m_scheduler)
   , m_writeHandle(m_face, m_storageHandle, m_keyChain, m_scheduler, m_validator)
+  , m_watchHandle(m_face, m_storageHandle, m_keyChain, m_scheduler, m_validator)
   , m_deleteHandle(m_face, m_storageHandle, m_keyChain, m_scheduler, m_validator)
   , m_tcpBulkInsertHandle(ioService, m_storageHandle)
 
@@ -143,6 +144,7 @@
        ++it)
     {
       m_writeHandle.listen(*it);
+      m_watchHandle.listen(*it);
       m_deleteHandle.listen(*it);
     }
 
diff --git a/src/repo.hpp b/src/repo.hpp
index abc25f4..ba518d1 100644
--- a/src/repo.hpp
+++ b/src/repo.hpp
@@ -26,6 +26,7 @@
 
 #include "handles/read-handle.hpp"
 #include "handles/write-handle.hpp"
+#include "handles/watch-handle.hpp"
 #include "handles/delete-handle.hpp"
 #include "handles/tcp-bulk-insert-handle.hpp"
 
@@ -87,6 +88,7 @@
   ValidatorConfig m_validator;
   ReadHandle m_readHandle;
   WriteHandle m_writeHandle;
+  WatchHandle m_watchHandle;
   DeleteHandle m_deleteHandle;
   TcpBulkInsertHandle m_tcpBulkInsertHandle;
 };
diff --git a/tests/integrated/test-basic-command-watch.cpp b/tests/integrated/test-basic-command-watch.cpp
new file mode 100644
index 0000000..ae06eb1
--- /dev/null
+++ b/tests/integrated/test-basic-command-watch.cpp
@@ -0,0 +1,267 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014,  Regents of the University of California.
+ *
+ * This file is part of NDN repo-ng (Next generation of NDN repository).
+ * See AUTHORS.md for complete list of repo-ng authors and contributors.
+ *
+ * repo-ng is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * repo-ng 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * repo-ng, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "handles/watch-handle.hpp"
+#include "storage/sqlite-storage.hpp"
+#include "common.hpp"
+
+#include "../repo-storage-fixture.hpp"
+#include "../dataset-fixtures.hpp"
+
+#include <ndn-cxx/util/random.hpp>
+#include <ndn-cxx/util/io.hpp>
+
+#include <boost/test/unit_test.hpp>
+#include <fstream>
+
+namespace repo {
+namespace tests {
+
+using ndn::time::milliseconds;
+using ndn::time::seconds;
+using ndn::EventId;
+namespace random=ndn::random;
+
+//All the test cases in this test suite should be run at once.
+BOOST_AUTO_TEST_SUITE(TestBasicCommandWatchDelete)
+
+const static uint8_t content[8] = {3, 1, 4, 1, 5, 9, 2, 6};
+
+template<class Dataset>
+class Fixture : public RepoStorageFixture, public Dataset
+{
+public:
+  Fixture()
+    : scheduler(repoFace.getIoService())
+    , validator(repoFace)
+    , watchHandle(repoFace, *handle, keyChain, scheduler, validator)
+    , watchFace(repoFace.getIoService())
+  {
+    watchHandle.listen(Name("/repo/command"));
+  }
+
+  ~Fixture()
+  {
+    repoFace.getIoService().stop();
+  }
+
+  void
+  generateDefaultCertificateFile();
+
+  void
+  scheduleWatchEvent();
+
+  void
+  onWatchInterest(const Interest& interest);
+
+  void
+  onRegisterFailed(const std::string& reason);
+
+  void
+  delayedInterest();
+
+  void
+  stopFaceProcess();
+
+  void
+  onWatchData(const Interest& interest, Data& data);
+
+  void
+  onWatchStopData(const Interest& interest, Data& data);
+
+  void
+  onWatchTimeout(const Interest& interest);
+
+  void
+  sendWatchStartInterest(const Interest& interest);
+
+  void
+  sendWatchStopInterest(const Interest& interest);
+
+  void
+  checkWatchOk(const Interest& interest);
+
+public:
+  Face repoFace;
+  Scheduler scheduler;
+  ValidatorConfig validator;
+  KeyChain keyChain;
+  WatchHandle watchHandle;
+  Face watchFace;
+  std::map<Name, EventId> watchEvents;
+};
+
+template<class T>  void
+Fixture<T>::generateDefaultCertificateFile()
+{
+  Name defaultIdentity = keyChain.getDefaultIdentity();
+  Name defaultKeyname = keyChain.getDefaultKeyNameForIdentity(defaultIdentity);
+  Name defaultCertficateName = keyChain.getDefaultCertificateNameForKey(defaultKeyname);
+  shared_ptr<ndn::IdentityCertificate> defaultCertficate =
+  keyChain.getCertificate(defaultCertficateName);
+  //test-integrated should run in root directory of repo-ng.
+  //certificate file should be removed after tests for security issue.
+  std::fstream certificateFile("tests/integrated/insert-delete-test.cert",
+                               std::ios::out | std::ios::binary | std::ios::trunc);
+  ndn::io::save(*defaultCertficate, certificateFile);
+  certificateFile.close();
+}
+
+template<class T> void
+Fixture<T>::onWatchInterest(const Interest& interest)
+{
+  shared_ptr<Data> data = make_shared<Data>(Name(interest.getName()).appendNumber(random::generateWord64()+100));
+  data->setContent(content, sizeof(content));
+  data->setFreshnessPeriod(milliseconds(0));
+  keyChain.signByIdentity(*data, keyChain.getDefaultIdentity());
+  watchFace.put(*data);
+
+  // schedule an event 50ms later to check whether watch is Ok
+  scheduler.scheduleEvent(milliseconds(10000),
+                          bind(&Fixture<T>::checkWatchOk, this,
+                               Interest(data->getName())));
+}
+
+
+template<class T> void
+Fixture<T>::onRegisterFailed(const std::string& reason)
+{
+  BOOST_ERROR("ERROR: Failed to register prefix in local hub's daemon" + reason);
+}
+
+template<class T> void
+Fixture<T>::delayedInterest()
+{
+  BOOST_ERROR("Fetching interest does not come. It may be satisfied in CS or something is wrong");
+}
+
+template<class T> void
+Fixture<T>::stopFaceProcess()
+{
+  repoFace.getIoService().stop();
+}
+
+template<class T> void
+Fixture<T>::onWatchData(const Interest& interest, Data& data)
+{
+  RepoCommandResponse response;
+  response.wireDecode(data.getContent().blockFromValue());
+
+  int statusCode = response.getStatusCode();
+  BOOST_CHECK_EQUAL(statusCode, 100);
+}
+
+template<class T> void
+Fixture<T>::onWatchStopData(const Interest& interest, Data& data)
+{
+  RepoCommandResponse response;
+  response.wireDecode(data.getContent().blockFromValue());
+
+  int statusCode = response.getStatusCode();
+  BOOST_CHECK_EQUAL(statusCode, 101);
+}
+
+template<class T> void
+Fixture<T>::onWatchTimeout(const Interest& interest)
+{
+  BOOST_ERROR("Watch command timeout");
+}
+
+template<class T> void
+Fixture<T>::sendWatchStartInterest(const Interest& watchInterest)
+{
+  watchFace.expressInterest(watchInterest,
+                            bind(&Fixture<T>::onWatchData, this, _1, _2),
+                            bind(&Fixture<T>::onWatchTimeout, this, _1));
+}
+
+template<class T> void
+Fixture<T>::sendWatchStopInterest(const Interest& watchInterest)
+{
+  watchFace.expressInterest(watchInterest,
+                            bind(&Fixture<T>::onWatchStopData, this, _1, _2),
+                            bind(&Fixture<T>::onWatchTimeout, this, _1));
+}
+
+template<class T> void
+Fixture<T>::checkWatchOk(const Interest& interest)
+{
+  BOOST_TEST_MESSAGE(interest);
+  shared_ptr<Data> data = handle->readData(interest);
+  if (data) {
+    int rc = memcmp(data->getContent().value(), content, sizeof(content));
+    BOOST_CHECK_EQUAL(rc, 0);
+  }
+  else {
+    std::cerr<<"Check Watch Failed"<<std::endl;
+  }
+}
+
+template<class T> void
+Fixture<T>::scheduleWatchEvent()
+{
+  Name watchCommandName("/repo/command/watch/start");
+  RepoCommandParameter watchParameter;
+  watchParameter.setName(Name("/a/b"));
+  watchParameter.setMaxInterestNum(10);
+  watchParameter.setInterestLifetime(milliseconds(50000));
+  watchParameter.setWatchTimeout(milliseconds(1000000000));
+  watchCommandName.append(watchParameter.wireEncode());
+  Interest watchInterest(watchCommandName);
+  keyChain.signByIdentity(watchInterest, keyChain.getDefaultIdentity());
+  //schedule a job to express watchInterest
+  scheduler.scheduleEvent(milliseconds(1000),
+                          bind(&Fixture<T>::sendWatchStartInterest, this, watchInterest));
+
+  Name watchStopName("/repo/command/watch/stop");
+  RepoCommandParameter watchStopParameter;
+  watchStopName.append(watchStopParameter.wireEncode());
+  Interest watchStopInterest(watchStopName);
+  keyChain.signByIdentity(watchStopInterest, keyChain.getDefaultIdentity());
+
+ // scheduler.scheduleEvent(milliseconds(10000),
+  //                        bind(&Fixture<T>::sendWatchStopInterest, this, watchStopInterest));
+  //The delayEvent will be canceled in onWatchInterest
+  watchFace.setInterestFilter(watchParameter.getName(),
+                              bind(&Fixture<T>::onWatchInterest, this, _2),
+                              ndn::RegisterPrefixSuccessCallback(),
+                              bind(&Fixture<T>::onRegisterFailed, this, _2));
+}
+
+typedef boost::mpl::vector< BasicDataset > Dataset;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(WatchDelete, T, Dataset, Fixture<T>)
+{
+  this->generateDefaultCertificateFile();
+  this->validator.load("tests/integrated/insert-delete-validator-config.conf");
+
+  // schedule events
+  this->scheduler.scheduleEvent(seconds(0),
+                                bind(&Fixture<T>::scheduleWatchEvent, this));
+
+  // schedule an event to terminate IO
+  this->scheduler.scheduleEvent(seconds(500),
+                                bind(&Fixture<T>::stopFaceProcess, this));
+  this->repoFace.getIoService().run();
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} //namespace tests
+} //namespace repo