full-producer.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2014-2024, The University of Memphis
4  *
5  * This file is part of PSync.
6  * See AUTHORS.md for complete list of PSync authors and contributors.
7  *
8  * PSync is free software: you can redistribute it and/or modify it under the terms
9  * of the GNU Lesser General Public License as published by the Free Software Foundation,
10  * either version 3 of the License, or (at your option) any later version.
11  *
12  * PSync is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  * PURPOSE. See the GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License along with
17  * PSync, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "PSync/full-producer.hpp"
21 #include "PSync/detail/state.hpp"
22 #include "PSync/detail/util.hpp"
23 
24 #include <ndn-cxx/lp/tags.hpp>
25 #include <ndn-cxx/security/validator-null.hpp>
26 #include <ndn-cxx/util/logger.hpp>
27 
28 #include <cstring>
29 
30 namespace psync {
31 
32 NDN_LOG_INIT(psync.FullProducer);
33 
35  ndn::KeyChain& keyChain,
36  const ndn::Name& syncPrefix,
37  const Options& opts)
38  : ProducerBase(face, keyChain, opts.ibfCount, syncPrefix, opts.syncDataFreshness,
39  opts.ibfCompression, opts.contentCompression)
40  , m_syncInterestLifetime(opts.syncInterestLifetime)
41  , m_onUpdate(opts.onUpdate)
42 {
43  m_registeredPrefix = m_face.setInterestFilter(ndn::InterestFilter(m_syncPrefix).allowLoopback(false),
44  [this] (auto&&... args) { onSyncInterest(std::forward<decltype(args)>(args)...); },
45  [] (auto&&... args) { onRegisterFailed(std::forward<decltype(args)>(args)...); });
46 
47  // Should we do this after setInterestFilter success call back
48  // (Currently following ChronoSync's way)
49  sendSyncInterest();
50 }
51 
53  ndn::KeyChain& keyChain,
54  size_t expectedNumEntries,
55  const ndn::Name& syncPrefix,
56  const ndn::Name& userPrefix,
57  UpdateCallback onUpdateCb,
58  ndn::time::milliseconds syncInterestLifetime,
59  ndn::time::milliseconds syncReplyFreshness,
60  CompressionScheme ibltCompression,
61  CompressionScheme contentCompression)
62  : FullProducer(face, keyChain, syncPrefix,
63  Options{std::move(onUpdateCb), static_cast<uint32_t>(expectedNumEntries), ibltCompression,
64  syncInterestLifetime, syncReplyFreshness, contentCompression})
65 {
66  addUserNode(userPrefix);
67 }
68 
70 {
71  if (m_fetcher) {
72  m_fetcher->stop();
73  }
74 }
75 
76 void
77 FullProducer::publishName(const ndn::Name& prefix, std::optional<uint64_t> seq)
78 {
79  if (m_prefixes.find(prefix) == m_prefixes.end()) {
80  NDN_LOG_WARN("Prefix not added: " << prefix);
81  return;
82  }
83 
84  uint64_t newSeq = seq.value_or(m_prefixes[prefix] + 1);
85  NDN_LOG_INFO("Publish: " << prefix << "/" << newSeq);
86  updateSeqNo(prefix, newSeq);
87 
88  m_inNoNewDataWaitOutPeriod = false;
89 
90  satisfyPendingInterests(ndn::Name(prefix).appendNumber(newSeq));
91 }
92 
93 void
94 FullProducer::sendSyncInterest()
95 {
96  if (m_inNoNewDataWaitOutPeriod) {
97  NDN_LOG_TRACE("Cannot send sync Interest as Data is expected from CS");
98  return;
99  }
100 
101  // If we send two sync interest one after the other
102  // since there is no new data in the network yet,
103  // when data is available it may satisfy both of them
104  if (m_fetcher) {
105  m_fetcher->stop();
106  }
107 
108  // Sync Interest format for full sync: /<sync-prefix>/<ourLatestIBF>
109  ndn::Name syncInterestName = m_syncPrefix;
110 
111  // Append our latest IBF
112  m_iblt.appendToName(syncInterestName);
113  // Append cumulative updates that has been inserted into this IBF
114  syncInterestName.appendNumber(m_numOwnElements);
115 
116  auto currentTime = ndn::time::system_clock::now();
117  if ((currentTime - m_lastInterestSentTime < ndn::time::milliseconds(MIN_JITTER)) &&
118  (m_outstandingInterestName == syncInterestName)) {
119  NDN_LOG_TRACE("Suppressing Interest: " << std::hash<ndn::Name>{}(syncInterestName));
120  return;
121  }
122 
123  m_outstandingInterestName = syncInterestName;
124 
125  m_scheduledSyncInterestId =
126  m_scheduler.schedule(m_syncInterestLifetime / 2 + ndn::time::milliseconds(m_jitter(m_rng)),
127  [this] { sendSyncInterest(); });
128 
129  ndn::Interest syncInterest(syncInterestName);
130 
131  using ndn::SegmentFetcher;
132  SegmentFetcher::Options options;
133  options.interestLifetime = m_syncInterestLifetime;
134  options.maxTimeout = m_syncInterestLifetime;
135  options.rttOptions.initialRto = m_syncInterestLifetime;
136 
137  // This log message must be before sending the Interest through SegmentFetcher
138  // because getNonce generates a Nonce for this Interest.
139  // SegmentFetcher makes a copy of this Interest, so if we print the Nonce
140  // after, that Nonce will be different than the one seen in tshark!
141  NDN_LOG_DEBUG("sendFullSyncInterest, nonce: " << syncInterest.getNonce() <<
142  ", hash: " << std::hash<ndn::Name>{}(syncInterestName));
143 
144  m_lastInterestSentTime = currentTime;
145  m_fetcher = SegmentFetcher::start(m_face, syncInterest,
146  ndn::security::getAcceptAllValidator(), options);
147 
148  m_fetcher->onComplete.connect([this, syncInterest] (const ndn::ConstBufferPtr& bufferPtr) {
149  onSyncData(syncInterest, bufferPtr);
150  });
151 
152  m_fetcher->afterSegmentValidated.connect([this] (const ndn::Data& data) {
153  auto tag = data.getTag<ndn::lp::IncomingFaceIdTag>();
154  if (tag) {
155  m_incomingFace = *tag;
156  }
157  else {
158  m_incomingFace = 0;
159  }
160  });
161 
162  m_fetcher->onError.connect([this] (uint32_t errorCode, const std::string& msg) {
163  NDN_LOG_ERROR("Cannot fetch sync data, error: " << errorCode << ", message: " << msg);
164  // We would like to recover from errors like NoRoute NACK quicker than sync Interest timeout.
165  // We don't react to Interest timeout here as we have scheduled the next sync Interest
166  // to be sent in half the sync Interest lifetime + jitter above. So we would react to
167  // timeout before it happens.
168  if (errorCode != SegmentFetcher::ErrorCode::INTEREST_TIMEOUT) {
169  auto after = ndn::time::milliseconds(m_jitter(m_rng));
170  NDN_LOG_DEBUG("Schedule sync Interest after: " << after);
171  m_scheduledSyncInterestId = m_scheduler.schedule(after, [this] { sendSyncInterest(); });
172  }
173  });
174 }
175 
176 void
177 FullProducer::processWaitingInterests()
178 {
179  NDN_LOG_TRACE("Processing waiting Interest list, size: " << m_waitingForProcessing.size());
180  if (m_waitingForProcessing.size() == 0) {
181  return;
182  }
183 
184  for (auto it = m_waitingForProcessing.begin(); it != m_waitingForProcessing.end();) {
185  if (it->second.numTries == std::numeric_limits<uint16_t>::max()) {
186  NDN_LOG_TRACE("Interest with hash already marked for deletion, removing now: " <<
187  std::hash<ndn::Name>{}(it->first));
188  it = m_waitingForProcessing.erase(it);
189  continue;
190  }
191 
192  it->second.numTries += 1;
193  ndn::Interest interest(it->first);
194  interest.setNonce(it->second.nonce);
195  onSyncInterest(m_syncPrefix, interest, true);
196  if (it->second.numTries == std::numeric_limits<uint16_t>::max()) {
197  NDN_LOG_TRACE("Removing Interest with hash: " << std::hash<ndn::Name>{}(it->first));
198  it = m_waitingForProcessing.erase(it);
199  }
200  else {
201  ++it;
202  }
203  }
204  NDN_LOG_TRACE("Done processing waiting Interest list, size: " << m_waitingForProcessing.size());
205 }
206 
207 void
208 FullProducer::scheduleProcessWaitingInterests()
209 {
210  // If nothing waiting, no need to schedule
211  if (m_waitingForProcessing.size() == 0) {
212  return;
213  }
214 
215  if (!m_interestDelayTimerId) {
216  auto after = ndn::time::milliseconds(m_jitter(m_rng));
217  NDN_LOG_TRACE("Setting a timer to processes waiting Interest(s) in: " << after);
218 
219  m_interestDelayTimerId = m_scheduler.schedule(after, [=] {
220  NDN_LOG_TRACE("Timer has expired, trying to process waiting Interest(s)");
221  processWaitingInterests();
222  scheduleProcessWaitingInterests();
223  });
224  }
225 }
226 
227 void
228 FullProducer::onSyncInterest(const ndn::Name& prefixName, const ndn::Interest& interest,
229  bool isTimedProcessing)
230 {
231  ndn::Name interestName = interest.getName();
232  auto interestNameHash = std::hash<ndn::Name>{}(interestName);
233  NDN_LOG_DEBUG("Full sync Interest received, nonce: " << interest.getNonce() <<
234  ", hash: " << interestNameHash);
235 
236  if (isTimedProcessing) {
237  NDN_LOG_TRACE("Delayed Interest being processed now");
238  }
239 
240  if (m_segmentPublisher.replyFromStore(interestName)) {
241  NDN_LOG_DEBUG("Answer from memory");
242  return;
243  }
244 
245  ndn::Name nameWithoutSyncPrefix = interestName.getSubName(prefixName.size());
246 
247  if (nameWithoutSyncPrefix.size() == 4) {
248  // /<IBF>/<numCumulativeElements>/<version>/<segment>
249  NDN_LOG_DEBUG("Segment not found in memory. Other side will have to restart");
250  // This should have been answered from publisher Cache!
251  sendApplicationNack(prefixName);
252  return;
253  }
254 
255  if (nameWithoutSyncPrefix.size() != 2) {
256  NDN_LOG_WARN("Two components required after sync prefix: /<IBF>/<numCumulativeElements>; received: " << interestName);
257  return;
258  }
259 
260  ndn::name::Component ibltName = interestName[-2];
261  uint64_t numRcvdElements = interestName[-1].toNumber();
262 
263  detail::IBLT iblt(m_expectedNumEntries, m_ibltCompression);
264  try {
265  iblt.initialize(ibltName);
266  }
267  catch (const std::exception& e) {
268  NDN_LOG_WARN(e.what());
269  return;
270  }
271 
272  auto diff = m_iblt - iblt;
273 
274  NDN_LOG_TRACE("Decode, positive: " << diff.positive.size()
275  << " negative: " << diff.negative.size() << " m_threshold: "
276  << m_threshold);
277 
278  auto waitingIt = m_waitingForProcessing.find(interestName);
279 
280  if (!diff.canDecode) {
281  NDN_LOG_DEBUG("Cannot decode differences!");
282 
283  if (numRcvdElements > m_numOwnElements) {
284  if (!isTimedProcessing && waitingIt == m_waitingForProcessing.end()) {
285  NDN_LOG_TRACE("Decode failure, adding to waiting Interest list " << interestNameHash);
286  m_waitingForProcessing.emplace(interestName, WaitingEntryInfo{0, interest.getNonce()});
287  scheduleProcessWaitingInterests();
288  }
289  else if (isTimedProcessing && waitingIt != m_waitingForProcessing.end()) {
290  if (waitingIt->second.numTries > 1) {
291  NDN_LOG_TRACE("Decode failure, still behind. Erasing waiting Interest as we have tried twice");
292  waitingIt->second.numTries = std::numeric_limits<uint16_t>::max(); // markWaitingInterestForDeletion
293  NDN_LOG_DEBUG("Waiting Interest has been deleted. Sending new sync interest");
294  sendSyncInterest();
295  }
296  else {
297  NDN_LOG_TRACE("Decode failure, still behind, waiting more till the next timer");
298  }
299  }
300  else {
301  NDN_LOG_TRACE("Decode failure, still behind");
302  }
303  }
304  else {
305  if (m_numOwnElements == numRcvdElements && diff.positive.size() == 0 && diff.negative.size() > 0) {
306  NDN_LOG_TRACE("We have nothing to offer and are actually behind");
307 #ifdef PSYNC_WITH_TESTS
308  ++nIbfDecodeFailuresBelowThreshold;
309 #endif // PSYNC_WITH_TESTS
310  return;
311  }
312 
313  detail::State state;
314  for (const auto& content : m_prefixes) {
315  if (content.second != 0) {
316  state.addContent(ndn::Name(content.first).appendNumber(content.second));
317  }
318  }
319 #ifdef PSYNC_WITH_TESTS
320  ++nIbfDecodeFailuresAboveThreshold;
321 #endif // PSYNC_WITH_TESTS
322 
323  if (!state.getContent().empty()) {
324  NDN_LOG_DEBUG("Sending entire state: " << state);
325  // Want low freshness when potentially sending large content to clear it quickly from the network
326  sendSyncData(interestName, state.wireEncode(), 10_ms);
327  // Since we're directly sending the data, we need to clear pending interests here
328  deletePendingInterests(interestName);
329  }
330  // We seem to be ahead, delete the Interest from waiting list
331  if (waitingIt != m_waitingForProcessing.end()) {
332  waitingIt->second.numTries = std::numeric_limits<uint16_t>::max();
333  }
334  }
335  return;
336  }
337 
338  if (diff.positive.size() == 0 && diff.negative.size() == 0) {
339  NDN_LOG_TRACE("Saving positive: " << diff.positive.size() << " negative: " << diff.negative.size());
340 
341  auto& entry = m_pendingEntries.emplace(interestName, PendingEntryInfo{iblt, {}}).first->second;
342  entry.expirationEvent = m_scheduler.schedule(interest.getInterestLifetime(),
343  [this, interest] {
344  NDN_LOG_TRACE("Erase pending Interest " << interest.getNonce());
345  m_pendingEntries.erase(interest.getName());
346  });
347 
348  // Can't delete directly in this case as it will cause
349  // memory access errors with the for loop in processWaitingInterests
350  if (isTimedProcessing) {
351  if (waitingIt != m_waitingForProcessing.end()) {
352  waitingIt->second.numTries = std::numeric_limits<uint16_t>::max();
353  }
354  }
355  return;
356  }
357 
358  // Only add to waiting list if we don't have anything to send (positive = 0)
359  if (diff.positive.size() == 0 && diff.negative.size() > 0) {
360  if (!isTimedProcessing && waitingIt == m_waitingForProcessing.end()) {
361  NDN_LOG_TRACE("Adding Interest to waiting list: " << interestNameHash);
362  m_waitingForProcessing.emplace(interestName, WaitingEntryInfo{0, interest.getNonce()});
363  scheduleProcessWaitingInterests();
364  }
365  else if (isTimedProcessing && waitingIt != m_waitingForProcessing.end()) {
366  if (waitingIt->second.numTries > 1) {
367  NDN_LOG_TRACE("Still behind after waiting for Interest " << interestNameHash <<
368  ". Erasing waiting Interest as we have tried twice");
369  waitingIt->second.numTries = std::numeric_limits<uint16_t>::max(); // markWaitingInterestForDeletion
370  }
371  else {
372  NDN_LOG_TRACE("Still behind after waiting for Interest " << interestNameHash <<
373  ". Keep waiting for Interest as number of tries is not exhausted");
374  }
375  }
376  else {
377  NDN_LOG_TRACE("Still behind after waiting for Interest " << interestNameHash);
378  }
379  return;
380  }
381 
382  if (diff.positive.size() > 0) {
383  detail::State state;
384  for (const auto& hash : diff.positive) {
385  auto nameIt = m_biMap.left.find(hash);
386  if (nameIt != m_biMap.left.end()) {
387  ndn::Name nameWithoutSeq = nameIt->second.getPrefix(-1);
388  // Don't sync up sequence number zero
389  if (m_prefixes[nameWithoutSeq] != 0 &&
390  !isFutureHash(nameWithoutSeq.toUri(), diff.negative)) {
391  state.addContent(nameIt->second);
392  }
393  }
394  }
395 
396  if (!state.getContent().empty()) {
397  NDN_LOG_DEBUG("Sending sync content: " << state);
398  sendSyncData(interestName, state.wireEncode(), m_syncReplyFreshness);
399 
400  // Timed processing or not - if we are answering it, it should not go in waiting Interests
401  if (waitingIt != m_waitingForProcessing.end()) {
402  waitingIt->second.numTries = std::numeric_limits<uint16_t>::max();
403  }
404  }
405  }
406 }
407 
408 void
409 FullProducer::sendSyncData(const ndn::Name& name, const ndn::Block& block,
410  ndn::time::milliseconds syncReplyFreshness)
411 {
412  bool isSatisfyingOwnInterest = m_outstandingInterestName == name;
413  if (isSatisfyingOwnInterest && m_fetcher) {
414  NDN_LOG_DEBUG("Removing our pending Interest from face (stop fetcher)");
415  m_fetcher->stop();
416  m_outstandingInterestName.clear();
417  }
418 
419  NDN_LOG_DEBUG("Sending sync Data");
420  auto content = detail::compress(m_contentCompression, block);
421  m_segmentPublisher.publish(name, name, *content, syncReplyFreshness);
422  if (isSatisfyingOwnInterest) {
423  NDN_LOG_DEBUG("Renewing sync interest");
424  sendSyncInterest();
425  }
426 }
427 
428 void
429 FullProducer::onSyncData(const ndn::Interest& interest, const ndn::ConstBufferPtr& bufferPtr)
430 {
431  deletePendingInterests(interest.getName());
432 
433  detail::State state;
434  try {
435  auto decompressed = detail::decompress(m_contentCompression, *bufferPtr);
436  state.wireDecode(ndn::Block(std::move(decompressed)));
437  }
438  catch (const std::exception& e) {
439  NDN_LOG_ERROR("Cannot parse received sync Data: " << e.what());
440  return;
441  }
442  NDN_LOG_DEBUG("Sync Data received: " << state);
443 
444  std::vector<MissingDataInfo> updates;
445 
446  for (const auto& content : state) {
447  ndn::Name prefix = content.getPrefix(-1);
448  uint64_t seq = content.get(content.size() - 1).toNumber();
449 
450  if (m_prefixes.find(prefix) == m_prefixes.end() || m_prefixes[prefix] < seq) {
451  updates.push_back({prefix, m_prefixes[prefix] + 1, seq, m_incomingFace});
452  updateSeqNo(prefix, seq);
453  // We should not call satisfyPendingSyncInterests here because we just
454  // got data and deleted pending interest by calling deletePendingFullSyncInterests
455  // But we might have interests not matching to this interest that might not have deleted
456  // from pending sync interest
457  }
458  }
459 
460  if (!updates.empty()) {
461  m_onUpdate(updates);
462  // Wait a bit to let neighbors get the data too
463  auto after = ndn::time::milliseconds(m_jitter(m_rng));
464  m_scheduledSyncInterestId = m_scheduler.schedule(after, [this] {
465  NDN_LOG_DEBUG("Got updates, renewing sync Interest now");
466  sendSyncInterest();
467  });
468  NDN_LOG_DEBUG("Schedule sync Interest after: " << after);
469  m_inNoNewDataWaitOutPeriod = false;
470 
471  processWaitingInterests();
472  }
473  else {
474  NDN_LOG_TRACE("No new update, Interest nonce: " << interest.getNonce() <<
475  " , hash: " << std::hash<ndn::Name>{}(interest.getName()));
476  m_inNoNewDataWaitOutPeriod = true;
477 
478  // Have to wait, otherwise will get same data from CS
479  auto after = m_syncReplyFreshness + ndn::time::milliseconds(m_jitter(m_rng));
480  m_scheduledSyncInterestId = m_scheduler.schedule(after, [this] {
481  NDN_LOG_DEBUG("Sending sync Interest after no new update");
482  m_inNoNewDataWaitOutPeriod = false;
483  sendSyncInterest();
484  });
485  NDN_LOG_DEBUG("Schedule sync after: " << after);
486  }
487 
488 }
489 
490 void
491 FullProducer::satisfyPendingInterests(const ndn::Name& updatedPrefixWithSeq)
492 {
493  NDN_LOG_DEBUG("Satisfying full sync Interest: " << m_pendingEntries.size());
494 
495  for (auto it = m_pendingEntries.begin(); it != m_pendingEntries.end();) {
496  NDN_LOG_TRACE("Satisfying pending Interest: " << std::hash<ndn::Name>{}(it->first.getPrefix(-1)));
497  const auto& entry = it->second;
498  auto diff = m_iblt - entry.iblt;
499  NDN_LOG_TRACE("Decoded: " << diff.canDecode << " positive: " << diff.positive.size() <<
500  " negative: " << diff.negative.size());
501 
502  detail::State state;
503  bool publishedPrefixInDiff = false;
504  for (const auto& hash : diff.positive) {
505  auto nameIt = m_biMap.left.find(hash);
506  if (nameIt != m_biMap.left.end()) {
507  if (updatedPrefixWithSeq == nameIt->second) {
508  publishedPrefixInDiff = true;
509  }
510  state.addContent(nameIt->second);
511  }
512  }
513 
514  if (!publishedPrefixInDiff) {
515  state.addContent(updatedPrefixWithSeq);
516  }
517 
518  NDN_LOG_DEBUG("Satisfying sync content: " << state);
519  sendSyncData(it->first, state.wireEncode(), m_syncReplyFreshness);
520  it = m_pendingEntries.erase(it);
521  }
522 }
523 
524 bool
525 FullProducer::isFutureHash(const ndn::Name& prefix, const std::set<uint32_t>& negative)
526 {
528  ndn::Name(prefix).appendNumber(m_prefixes[prefix] + 1));
529  return negative.find(nextHash) != negative.end();
530 }
531 
532 void
533 FullProducer::deletePendingInterests(const ndn::Name& interestName)
534 {
535  auto it = m_pendingEntries.find(interestName);
536  if (it != m_pendingEntries.end()) {
537  NDN_LOG_TRACE("Delete pending Interest: " << std::hash<ndn::Name>{}(interestName));
538  it = m_pendingEntries.erase(it);
539  }
540 }
541 
542 } // namespace psync
Full sync logic to synchronize with other nodes where all nodes wants to get all names prefixes synce...
void publishName(const ndn::Name &prefix, std::optional< uint64_t > seq=std::nullopt)
Publish name to let others know.
FullProducer(ndn::Face &face, ndn::KeyChain &keyChain, const ndn::Name &syncPrefix, const Options &opts)
Constructor.
Base class for PartialProducer and FullProducer.
const CompressionScheme m_contentCompression
void sendApplicationNack(const ndn::Name &name)
Sends a data packet with content type nack.
const ndn::Name m_syncPrefix
const ndn::time::milliseconds m_syncReplyFreshness
const size_t m_expectedNumEntries
bool addUserNode(const ndn::Name &prefix)
Adds a user node for synchronization.
std::map< ndn::Name, uint64_t > m_prefixes
const size_t m_threshold
HashNameBiMap m_biMap
ndn::Scheduler m_scheduler
static void onRegisterFailed(const ndn::Name &prefix, const std::string &msg)
Logs a message and throws if setting an interest filter fails.
const CompressionScheme m_ibltCompression
void updateSeqNo(const ndn::Name &prefix, uint64_t seq)
Update m_prefixes and IBF with the given prefix and seq.
SegmentPublisher m_segmentPublisher
ndn::random::RandomNumberEngine & m_rng
bool replyFromStore(const ndn::Name &interestName)
Try to reply from memory, return false if we cannot find the segment.
void publish(const ndn::Name &interestName, const ndn::Name &dataName, ndn::span< const uint8_t > buffer, ndn::time::milliseconds freshness)
Put all the segments in memory.
void appendToName(ndn::Name &name) const
Appends self to name.
Definition: iblt.cpp:138
uint32_t murmurHash3(const void *key, size_t len, uint32_t seed)
Definition: util.cpp:58
constexpr size_t N_HASHCHECK
Definition: iblt.hpp:87
std::shared_ptr< ndn::Buffer > compress(CompressionScheme scheme, ndn::span< const uint8_t > buffer)
Definition: util.cpp:124
std::shared_ptr< ndn::Buffer > decompress(CompressionScheme scheme, ndn::span< const uint8_t > buffer)
Definition: util.cpp:183
Definition: common.hpp:34
CompressionScheme
Definition: common.hpp:43
std::function< void(const std::vector< MissingDataInfo > &)> UpdateCallback
Definition: common.hpp:71
Constructor options.