blob: 684009d09bfbcade275920e8e03ba1229635828b [file] [log] [blame]
akmhoque3d06e792014-05-27 16:23:20 -05001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Nick Gordonfeae5572017-01-13 12:06:26 -06003 * Copyright (c) 2014-2017, The University of Memphis,
Nick Gordonf8b5bcd2016-08-11 15:06:50 -05004 * Regents of the University of California
akmhoque3d06e792014-05-27 16:23:20 -05005 *
6 * This file is part of NLSR (Named-data Link State Routing).
7 * See AUTHORS.md for complete list of NLSR authors and contributors.
8 *
9 * NLSR is free software: you can redistribute it and/or modify it under the terms
10 * of the GNU General Public License as published by the Free Software Foundation,
11 * either version 3 of the License, or (at your option) any later version.
12 *
13 * NLSR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
14 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along with
18 * NLSR, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * \author A K M Mahmudul Hoque <ahoque1@memphis.edu>
21 *
22 **/
Junxiao Shi63bd0342016-08-17 16:57:14 +000023
akmhoque31d1d4b2014-05-05 22:08:14 -050024#include "nlsr.hpp"
25#include "lsdb.hpp"
26#include "hello-protocol.hpp"
27#include "utility/name-helper.hpp"
akmhoque674b0b12014-05-20 14:33:28 -050028#include "logger.hpp"
akmhoque31d1d4b2014-05-05 22:08:14 -050029
30namespace nlsr {
31
akmhoque674b0b12014-05-20 14:33:28 -050032INIT_LOGGER("HelloProtocol");
33
akmhoque93f1a072014-06-19 16:24:28 -050034const std::string HelloProtocol::INFO_COMPONENT = "INFO";
35const std::string HelloProtocol::NLSR_COMPONENT = "NLSR";
akmhoque157b0a42014-05-13 00:26:37 -050036
akmhoque31d1d4b2014-05-05 22:08:14 -050037void
38HelloProtocol::expressInterest(const ndn::Name& interestName, uint32_t seconds)
39{
akmhoque674b0b12014-05-20 14:33:28 -050040 _LOG_DEBUG("Expressing Interest :" << interestName);
akmhoque31d1d4b2014-05-05 22:08:14 -050041 ndn::Interest i(interestName);
42 i.setInterestLifetime(ndn::time::seconds(seconds));
43 i.setMustBeFresh(true);
44 m_nlsr.getNlsrFace().expressInterest(i,
dmcoomes9f936662017-03-02 10:33:09 -060045 std::bind(&HelloProtocol::onContent,
akmhoque31d1d4b2014-05-05 22:08:14 -050046 this,
47 _1, _2),
Alexander Afanasyev1de901f2017-03-09 12:43:57 -080048 std::bind(&HelloProtocol::processInterestTimedOut, // Nack
49 this, _1),
dmcoomes9f936662017-03-02 10:33:09 -060050 std::bind(&HelloProtocol::processInterestTimedOut,
akmhoque31d1d4b2014-05-05 22:08:14 -050051 this, _1));
52}
53
54void
55HelloProtocol::sendScheduledInterest(uint32_t seconds)
56{
57 std::list<Adjacent> adjList = m_nlsr.getAdjacencyList().getAdjList();
58 for (std::list<Adjacent>::iterator it = adjList.begin(); it != adjList.end();
akmhoque157b0a42014-05-13 00:26:37 -050059 ++it) {
Nick G97e34942016-07-11 14:46:27 -050060 // If this adjacency has a Face, just proceed as usual.
akmhoquec04e7272014-07-02 11:00:14 -050061 if((*it).getFaceId() != 0) {
dmcoomes9f936662017-03-02 10:33:09 -060062 // interest name: /<neighbor>/NLSR/INFO/<router>
akmhoquec04e7272014-07-02 11:00:14 -050063 ndn::Name interestName = (*it).getName() ;
64 interestName.append(NLSR_COMPONENT);
65 interestName.append(INFO_COMPONENT);
66 interestName.append(m_nlsr.getConfParameter().getRouterPrefix().wireEncode());
67 expressInterest(interestName,
68 m_nlsr.getConfParameter().getInterestResendTime());
dmcoomes9eaf3f42017-02-21 11:39:01 -060069 _LOG_DEBUG("Sending scheduled interest: " << interestName);
akmhoquec04e7272014-07-02 11:00:14 -050070 }
Nick G97e34942016-07-11 14:46:27 -050071 // If it does not have a Face, we need to give it one. A
72 // successful registration prompts a callback that sends the hello
73 // Interest to the new Face.
akmhoquec04e7272014-07-02 11:00:14 -050074 else {
75 registerPrefixes((*it).getName(), (*it).getConnectingFaceUri(),
akmhoquebf11c5f2014-07-21 14:49:47 -050076 (*it).getLinkCost(), ndn::time::milliseconds::max());
akmhoquec04e7272014-07-02 11:00:14 -050077 }
akmhoque31d1d4b2014-05-05 22:08:14 -050078 }
79 scheduleInterest(m_nlsr.getConfParameter().getInfoInterestInterval());
80}
81
82void
83HelloProtocol::scheduleInterest(uint32_t seconds)
84{
Vince Lehman50df6b72015-03-03 12:06:40 -060085 _LOG_DEBUG("Scheduling HELLO Interests in " << ndn::time::seconds(seconds));
86
Vince Lehman7c603292014-09-11 17:48:16 -050087 m_scheduler.scheduleEvent(ndn::time::seconds(seconds),
dmcoomes9f936662017-03-02 10:33:09 -060088 std::bind(&HelloProtocol::sendScheduledInterest, this, seconds));
akmhoque31d1d4b2014-05-05 22:08:14 -050089}
90
91void
92HelloProtocol::processInterest(const ndn::Name& name,
93 const ndn::Interest& interest)
94{
dmcoomes9f936662017-03-02 10:33:09 -060095 // interest name: /<neighbor>/NLSR/INFO/<router>
akmhoque31d1d4b2014-05-05 22:08:14 -050096 const ndn::Name interestName = interest.getName();
akmhoque674b0b12014-05-20 14:33:28 -050097 _LOG_DEBUG("Interest Received for Name: " << interestName);
akmhoque157b0a42014-05-13 00:26:37 -050098 if (interestName.get(-2).toUri() != INFO_COMPONENT) {
dmcoomes9eaf3f42017-02-21 11:39:01 -060099 _LOG_DEBUG("INFO_COMPONENT not found or interestName: " << interestName
100 << " does not match expression");
akmhoque31d1d4b2014-05-05 22:08:14 -0500101 return;
102 }
akmhoque157b0a42014-05-13 00:26:37 -0500103 ndn::Name neighbor;
104 neighbor.wireDecode(interestName.get(-1).blockFromValue());
akmhoque674b0b12014-05-20 14:33:28 -0500105 _LOG_DEBUG("Neighbor: " << neighbor);
akmhoque157b0a42014-05-13 00:26:37 -0500106 if (m_nlsr.getAdjacencyList().isNeighbor(neighbor)) {
dmcoomes9f936662017-03-02 10:33:09 -0600107 std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>();
akmhoque69c9aa92014-07-23 15:15:05 -0500108 data->setName(ndn::Name(interest.getName()).appendVersion());
109 data->setFreshnessPeriod(ndn::time::seconds(10)); // 10 sec
110 data->setContent(reinterpret_cast<const uint8_t*>(INFO_COMPONENT.c_str()),
akmhoque157b0a42014-05-13 00:26:37 -0500111 INFO_COMPONENT.size());
akmhoque69c9aa92014-07-23 15:15:05 -0500112 m_nlsr.getKeyChain().sign(*data, m_nlsr.getDefaultCertName());
113 _LOG_DEBUG("Sending out data for name: " << interest.getName());
114 m_nlsr.getNlsrFace().put(*data);
dmcoomes9eaf3f42017-02-21 11:39:01 -0600115 Adjacent* adjacent = m_nlsr.getAdjacencyList().findAdjacent(neighbor);
Nick G97e34942016-07-11 14:46:27 -0500116 // If this neighbor was previously inactive, send our own hello interest, too
Vince Lehmancb76ade2014-08-28 21:24:41 -0500117 if (adjacent->getStatus() == Adjacent::STATUS_INACTIVE) {
Nick G97e34942016-07-11 14:46:27 -0500118 // We can only do that if the neighbor currently has a face.
akmhoquec04e7272014-07-02 11:00:14 -0500119 if(adjacent->getFaceId() != 0){
dmcoomes9f936662017-03-02 10:33:09 -0600120 // interest name: /<neighbor>/NLSR/INFO/<router>
akmhoquec04e7272014-07-02 11:00:14 -0500121 ndn::Name interestName(neighbor);
122 interestName.append(NLSR_COMPONENT);
123 interestName.append(INFO_COMPONENT);
124 interestName.append(m_nlsr.getConfParameter().getRouterPrefix().wireEncode());
125 expressInterest(interestName,
126 m_nlsr.getConfParameter().getInterestResendTime());
127 }
Nick G97e34942016-07-11 14:46:27 -0500128 // If the originator of the Interest currently lacks a Face, we
129 // need to give it one.
akmhoquec04e7272014-07-02 11:00:14 -0500130 else {
131 registerPrefixes(adjacent->getName(), adjacent->getConnectingFaceUri(),
akmhoquebf11c5f2014-07-21 14:49:47 -0500132 adjacent->getLinkCost(), ndn::time::milliseconds::max());
akmhoquec04e7272014-07-02 11:00:14 -0500133 }
akmhoque31d1d4b2014-05-05 22:08:14 -0500134 }
135 }
136}
137
138void
139HelloProtocol::processInterestTimedOut(const ndn::Interest& interest)
140{
dmcoomes9f936662017-03-02 10:33:09 -0600141 // interest name: /<neighbor>/NLSR/INFO/<router>
akmhoque31d1d4b2014-05-05 22:08:14 -0500142 const ndn::Name interestName(interest.getName());
akmhoque674b0b12014-05-20 14:33:28 -0500143 _LOG_DEBUG("Interest timed out for Name: " << interestName);
akmhoque157b0a42014-05-13 00:26:37 -0500144 if (interestName.get(-2).toUri() != INFO_COMPONENT) {
akmhoque31d1d4b2014-05-05 22:08:14 -0500145 return;
146 }
akmhoque93f1a072014-06-19 16:24:28 -0500147 ndn::Name neighbor = interestName.getPrefix(-3);
akmhoque674b0b12014-05-20 14:33:28 -0500148 _LOG_DEBUG("Neighbor: " << neighbor);
akmhoque31d1d4b2014-05-05 22:08:14 -0500149 m_nlsr.getAdjacencyList().incrementTimedOutInterestCount(neighbor);
Vince Lehmancb76ade2014-08-28 21:24:41 -0500150
151 Adjacent::Status status = m_nlsr.getAdjacencyList().getStatusOfNeighbor(neighbor);
152
akmhoque31d1d4b2014-05-05 22:08:14 -0500153 uint32_t infoIntTimedOutCount =
154 m_nlsr.getAdjacencyList().getTimedOutInterestCount(neighbor);
akmhoque674b0b12014-05-20 14:33:28 -0500155 _LOG_DEBUG("Status: " << status);
156 _LOG_DEBUG("Info Interest Timed out: " << infoIntTimedOutCount);
akmhoque157b0a42014-05-13 00:26:37 -0500157 if ((infoIntTimedOutCount < m_nlsr.getConfParameter().getInterestRetryNumber())) {
dmcoomes9f936662017-03-02 10:33:09 -0600158 // interest name: /<neighbor>/NLSR/INFO/<router>
akmhoque31d1d4b2014-05-05 22:08:14 -0500159 ndn::Name interestName(neighbor);
akmhoque93f1a072014-06-19 16:24:28 -0500160 interestName.append(NLSR_COMPONENT);
akmhoque157b0a42014-05-13 00:26:37 -0500161 interestName.append(INFO_COMPONENT);
162 interestName.append(m_nlsr.getConfParameter().getRouterPrefix().wireEncode());
dmcoomes9eaf3f42017-02-21 11:39:01 -0600163 _LOG_DEBUG("Resending interest: " << interestName);
akmhoque31d1d4b2014-05-05 22:08:14 -0500164 expressInterest(interestName,
165 m_nlsr.getConfParameter().getInterestResendTime());
166 }
Vince Lehmancb76ade2014-08-28 21:24:41 -0500167 else if ((status == Adjacent::STATUS_ACTIVE) &&
akmhoque157b0a42014-05-13 00:26:37 -0500168 (infoIntTimedOutCount == m_nlsr.getConfParameter().getInterestRetryNumber())) {
Vince Lehmancb76ade2014-08-28 21:24:41 -0500169 m_nlsr.getAdjacencyList().setStatusOfNeighbor(neighbor, Adjacent::STATUS_INACTIVE);
Vince Lehman50df6b72015-03-03 12:06:40 -0600170
dmcoomes9eaf3f42017-02-21 11:39:01 -0600171 _LOG_DEBUG("Neighbor: " << neighbor << " status changed to INACTIVE");
172
Vince Lehman50df6b72015-03-03 12:06:40 -0600173 m_nlsr.getLsdb().scheduleAdjLsaBuild();
akmhoque31d1d4b2014-05-05 22:08:14 -0500174 }
175}
176
Nick G97e34942016-07-11 14:46:27 -0500177 // This is the first function that incoming Hello data will
178 // see. This checks if the data appears to be signed, and passes it
179 // on to validate the content of the data.
Yingdi Yu20e3a6e2014-05-26 23:16:10 -0700180void
181HelloProtocol::onContent(const ndn::Interest& interest, const ndn::Data& data)
182{
akmhoquedfe615f2014-07-27 14:12:21 -0500183 _LOG_DEBUG("Received data for INFO(name): " << data.getName());
184 if (data.getSignature().hasKeyLocator()) {
185 if (data.getSignature().getKeyLocator().getType() == ndn::KeyLocator::KeyLocator_Name) {
186 _LOG_DEBUG("Data signed with: " << data.getSignature().getKeyLocator().getName());
187 }
188 }
Yingdi Yu20e3a6e2014-05-26 23:16:10 -0700189 m_nlsr.getValidator().validate(data,
dmcoomes9f936662017-03-02 10:33:09 -0600190 std::bind(&HelloProtocol::onContentValidated, this, _1),
191 std::bind(&HelloProtocol::onContentValidationFailed,
Yingdi Yu20e3a6e2014-05-26 23:16:10 -0700192 this, _1, _2));
193}
akmhoque31d1d4b2014-05-05 22:08:14 -0500194
Nick G97e34942016-07-11 14:46:27 -0500195 // A validator is called on the incoming data, and if the data
196 // passes the validator's description/definitions, this function is
197 // called. Set the neighbor's status to active and refresh its
198 // LSA. If there was a change in status, we schedule an adjacency
199 // LSA build.
akmhoque31d1d4b2014-05-05 22:08:14 -0500200void
dmcoomes9f936662017-03-02 10:33:09 -0600201HelloProtocol::onContentValidated(const std::shared_ptr<const ndn::Data>& data)
akmhoque31d1d4b2014-05-05 22:08:14 -0500202{
dmcoomes9f936662017-03-02 10:33:09 -0600203 // data name: /<neighbor>/NLSR/INFO/<router>/<version>
Yingdi Yu20e3a6e2014-05-26 23:16:10 -0700204 ndn::Name dataName = data->getName();
akmhoquedfe615f2014-07-27 14:12:21 -0500205 _LOG_DEBUG("Data validation successful for INFO(name): " << dataName);
akmhoque157b0a42014-05-13 00:26:37 -0500206 if (dataName.get(-3).toUri() == INFO_COMPONENT) {
akmhoque93f1a072014-06-19 16:24:28 -0500207 ndn::Name neighbor = dataName.getPrefix(-4);
Vince Lehmancb76ade2014-08-28 21:24:41 -0500208
209 Adjacent::Status oldStatus = m_nlsr.getAdjacencyList().getStatusOfNeighbor(neighbor);
210 m_nlsr.getAdjacencyList().setStatusOfNeighbor(neighbor, Adjacent::STATUS_ACTIVE);
akmhoque31d1d4b2014-05-05 22:08:14 -0500211 m_nlsr.getAdjacencyList().setTimedOutInterestCount(neighbor, 0);
Vince Lehmancb76ade2014-08-28 21:24:41 -0500212 Adjacent::Status newStatus = m_nlsr.getAdjacencyList().getStatusOfNeighbor(neighbor);
213
akmhoque2f423352014-06-03 11:49:35 -0500214 _LOG_DEBUG("Neighbor : " << neighbor);
akmhoque674b0b12014-05-20 14:33:28 -0500215 _LOG_DEBUG("Old Status: " << oldStatus << " New Status: " << newStatus);
akmhoque157b0a42014-05-13 00:26:37 -0500216 // change in Adjacency list
217 if ((oldStatus - newStatus) != 0) {
Ashlesh Gawandec5fa3202016-12-05 13:21:51 -0600218 if (m_nlsr.getConfParameter().getHyperbolicState() == HYPERBOLIC_STATE_ON) {
219 m_nlsr.getRoutingTable().scheduleRoutingTableCalculation(m_nlsr);
220 }
221 else {
222 m_nlsr.getLsdb().scheduleAdjLsaBuild();
223 }
akmhoque31d1d4b2014-05-05 22:08:14 -0500224 }
225 }
226}
227
Nick G97e34942016-07-11 14:46:27 -0500228 // Simply logs a debug message that the content could not be
229 // validated (and is implicitly being discarded as a result).
Yingdi Yu20e3a6e2014-05-26 23:16:10 -0700230void
dmcoomes9f936662017-03-02 10:33:09 -0600231HelloProtocol::onContentValidationFailed(const std::shared_ptr<const ndn::Data>& data,
Yingdi Yu20e3a6e2014-05-26 23:16:10 -0700232 const std::string& msg)
233{
234 _LOG_DEBUG("Validation Error: " << msg);
235}
236
Nick G97e34942016-07-11 14:46:27 -0500237
238 // Asks the FIB to register the supplied adjacency (in other words,
239 // create a Face for it).
akmhoquec04e7272014-07-02 11:00:14 -0500240void
akmhoque8e0252b2014-07-07 16:04:44 -0500241HelloProtocol::registerPrefixes(const ndn::Name& adjName, const std::string& faceUri,
akmhoquebf11c5f2014-07-21 14:49:47 -0500242 double linkCost, const ndn::time::milliseconds& timeout)
akmhoquec04e7272014-07-02 11:00:14 -0500243{
akmhoque060d3022014-08-12 13:35:06 -0500244 m_nlsr.getFib().registerPrefix(adjName, faceUri, linkCost, timeout,
245 ndn::nfd::ROUTE_FLAG_CAPTURE, 0,
dmcoomes9f936662017-03-02 10:33:09 -0600246 std::bind(&HelloProtocol::onRegistrationSuccess,
akmhoque102aea42014-08-04 10:22:12 -0500247 this, _1, adjName,timeout),
dmcoomes9f936662017-03-02 10:33:09 -0600248 std::bind(&HelloProtocol::onRegistrationFailure,
Junxiao Shi63bd0342016-08-17 16:57:14 +0000249 this, _1, adjName));
akmhoquec04e7272014-07-02 11:00:14 -0500250}
251
Nick G97e34942016-07-11 14:46:27 -0500252 // After we create a new Face, we need to set it up for use. This
253 // function sets the controlling strategy, registers prefixes in
254 // sync, broadcast, and LSA.
akmhoquec04e7272014-07-02 11:00:14 -0500255void
256HelloProtocol::onRegistrationSuccess(const ndn::nfd::ControlParameters& commandSuccessResult,
akmhoque102aea42014-08-04 10:22:12 -0500257 const ndn::Name& neighbor,const ndn::time::milliseconds& timeout)
akmhoquec04e7272014-07-02 11:00:14 -0500258{
dmcoomes9eaf3f42017-02-21 11:39:01 -0600259 Adjacent* adjacent = m_nlsr.getAdjacencyList().findAdjacent(neighbor);
akmhoquec04e7272014-07-02 11:00:14 -0500260 if (adjacent != 0) {
261 adjacent->setFaceId(commandSuccessResult.getFaceId());
akmhoque102aea42014-08-04 10:22:12 -0500262 ndn::Name broadcastKeyPrefix = DEFAULT_BROADCAST_PREFIX;
263 broadcastKeyPrefix.append("KEYS");
264 std::string faceUri = adjacent->getConnectingFaceUri();
265 double linkCost = adjacent->getLinkCost();
266 m_nlsr.getFib().registerPrefix(m_nlsr.getConfParameter().getChronosyncPrefix(),
akmhoque060d3022014-08-12 13:35:06 -0500267 faceUri, linkCost, timeout,
268 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
akmhoque102aea42014-08-04 10:22:12 -0500269 m_nlsr.getFib().registerPrefix(m_nlsr.getConfParameter().getLsaPrefix(),
akmhoque060d3022014-08-12 13:35:06 -0500270 faceUri, linkCost, timeout,
271 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
akmhoque102aea42014-08-04 10:22:12 -0500272 m_nlsr.getFib().registerPrefix(broadcastKeyPrefix,
akmhoque060d3022014-08-12 13:35:06 -0500273 faceUri, linkCost, timeout,
274 ndn::nfd::ROUTE_FLAG_CAPTURE, 0);
akmhoque102aea42014-08-04 10:22:12 -0500275
Nick G97e34942016-07-11 14:46:27 -0500276 // Sends a Hello Interest to determine status before the next scheduled.
dmcoomes9f936662017-03-02 10:33:09 -0600277 // interest name: /<neighbor>/NLSR/INFO/<router>
akmhoquec04e7272014-07-02 11:00:14 -0500278 ndn::Name interestName(neighbor);
279 interestName.append(NLSR_COMPONENT);
280 interestName.append(INFO_COMPONENT);
281 interestName.append(m_nlsr.getConfParameter().getRouterPrefix().wireEncode());
282 expressInterest(interestName,
283 m_nlsr.getConfParameter().getInterestResendTime());
284 }
285}
286
287void
Junxiao Shi63bd0342016-08-17 16:57:14 +0000288HelloProtocol::onRegistrationFailure(const ndn::nfd::ControlResponse& response,
akmhoquedfe615f2014-07-27 14:12:21 -0500289 const ndn::Name& name)
akmhoquec04e7272014-07-02 11:00:14 -0500290{
Junxiao Shi63bd0342016-08-17 16:57:14 +0000291 _LOG_DEBUG(response.getText() << " (code: " << response.getCode() << ")");
akmhoquedfe615f2014-07-27 14:12:21 -0500292 /*
293 * If NLSR can not create face for given faceUri then it will treat this
294 * failure as one INFO interest timed out. So that NLSR can move on with
295 * building Adj Lsa and calculate routing table. NLSR does not build Adj
296 * Lsa unless all the neighbors are ACTIVE or DEAD. For considering the
297 * missconfigured(link) neighbour dead this is required.
298 */
dmcoomes9eaf3f42017-02-21 11:39:01 -0600299 Adjacent* adjacent = m_nlsr.getAdjacencyList().findAdjacent(name);
akmhoquedfe615f2014-07-27 14:12:21 -0500300 if (adjacent != 0) {
301 adjacent->setInterestTimedOutNo(adjacent->getInterestTimedOutNo() + 1);
Vince Lehmancb76ade2014-08-28 21:24:41 -0500302 Adjacent::Status status = adjacent->getStatus();
akmhoquedfe615f2014-07-27 14:12:21 -0500303 uint32_t infoIntTimedOutCount = adjacent->getInterestTimedOutNo();
304
305 if (infoIntTimedOutCount == m_nlsr.getConfParameter().getInterestRetryNumber()) {
Vince Lehmancb76ade2014-08-28 21:24:41 -0500306 if (status == Adjacent::STATUS_ACTIVE) {
307 adjacent->setStatus(Adjacent::STATUS_INACTIVE);
akmhoquedfe615f2014-07-27 14:12:21 -0500308 }
Vince Lehman50df6b72015-03-03 12:06:40 -0600309
310 m_nlsr.getLsdb().scheduleAdjLsaBuild();
akmhoquedfe615f2014-07-27 14:12:21 -0500311 }
312 }
akmhoquec04e7272014-07-02 11:00:14 -0500313}
314
Nick Gordonfad8e252016-08-11 14:21:38 -0500315} // namespace nlsr