NDNph is my latest Named Data Networking (NDN) client library. This article gives an overview of this library.
History and Motivation
In 2016, I started esp8266ndn. It contains a copy of ndn-cpp-lite, UCLA REMAP's C++ library that does not use dynamic memory allocations. I then added integrations with ESP8266's network stack and crypto functions, making esp8266ndn the first NDN library that works on the ESP8266 microcontroller. Using this library, I built several projects, including a wearable jewelry and a call button. University of Memphis also deployed several sensor nodes using esp8266ndn.
Over the years, esp8266ndn gained many new features, and widened platform support to include ESP32 and nRF52. However, using ndn-cpp-lite as the core is becoming problematic:
- New protocol features show up slowly, because ndn-cpp-lite author would not add a feature until ndn-cxx has it, and design discussions for ndn-cxx sometimes take several years.
- There is no generic TLV encoder/decoder, making it difficult to support TLV structures in application layer.
- ndn-cpp-lite is bloated with obsolete features, due to their backwards compatibility guarantees. Consequently, binary code size is unnecessarily large.
- Although I can add patches to ndn-cpp-lite during importing into esp8266ndn, it has been difficult to test these patches. This isn't ndn-cpp-lite's fault, but is still an issue.
After publishing NDNts, I thought: why not rip ndn-cpp-lite out of esp8266ndn, and replace it with my own?
Learning from the Giants
ArduinoJson is one of the best Arduino libraries. I spent some time reading through their API, and liked several aspects:
- It has a region-based memory allocator, which owns the memory associated with everything related to a JSON document.
- It is a header-only library, making it highly portable: it works both in Arduino IDE, and on other platforms with a C++ compiler.
- Another benefit of being header-only is that, the application can specify compile time configuration by defining macros above
#include <ArduinoJson.h>
, without modifying the source code of ArduinoJson library.
I also learned from NDN-Lite, an embedded NDN stack written in C language. Although I dislike many of their APIs, they did one thing right:
- NDN-Lite does not try to support all platforms with the same codebase. Instead, maintain a "core" codebase, and several "adaptations" (i.e. ports) for different platforms.
Design Decisions
Following the example of NDN-Lite, the "core" of esp8266ndn would be in a separate library, NDNph. In this name, "p" means "packet", as this library provides NDN packet encoding, among other features. "h" means "headers", which indicates that this is a header-only library. Then, esp8266ndn becomes a port of NDNph.
Memory management is the most important aspect of any C and C++ library. Following the example of ArduinoJson, NDNph adopts region-based memory management.
NDNph supports multiple platforms, big and small. Currently, NDNph supports Linux, and esp8266ndn supports ESP8266, ESP32, and nRF52. This in turn simplifies unit testing for NDNph, because I can run most tests on a computer and in the cloud, instead of painstakingly making them work on the ESP8266. However, this does limit the codebase to C++11 because that's what Arduino is using, instead of the newer C++17 standard.
Like most of my other projects, I don't care much about backwards compatibility. Being a personal project, I have to prioritize in developing for the latest and greatest, instead of worrying about backwards compatibility.
NDNph Architecture
Unlike the many packages in NDNts, NDNph is a single library. I'd give a brief introduction of its components.
We start from the boring part, memory management:
Region
class is the region-based memory allocator.- You may either use
StaticRegion
class template to reserve memory on the stack, orDynamicRegion
class to reserve memory on the heap. - It's recommended to create a new
Region
for each packet you are sending or processing, and discard it as soon as you finish working with that packet.
The memory allocator allows us to have packet representation and encoding:
Encoder
andDecoder
implement Type-Length-Value (TLV) structure encoder and decoder.EvDecoder
is a decoder that is aware of TLV evolvability guidelines. The API is similar to its NDNts counterpart, but may be tricky to understand in C++.Component
,Name
,Interest
,Data
, andNack
types have their usual meaning.ndnph::convention
namespace supports NDN Naming Conventions rev2.
We need a KeyChain to secure the packets:
PrivateKey
is something that can sign a packet.PublicKey
is something that can verify signature on a packet.DigestKey
is both a PrivateKey and a PublicKey, but it only provides integrity protection.EcdsaPrivateKey
andEcdsaPublicKey
can create and verify ECDSA signatures.- Currently, there is no key management or trust schema support.
To send or receive packets:
Transport
is a low-level transport that transmits and receives packets as buffers. Each port is expected to implement a few subclasses ofTransport
.Face
wraps aroundTransport
to encode/decode packets.PacketHandler
is the base class of (a component of) your application. You can overrideprocessInterest
,processData
, andprocessNack
methods to receive packets, and invokesend
orreply
methods to send packets. This is generally where you start developing an application.- Multiple
PacketHandler
can be attached to the sameFace
. See this article for an explanation of my design.
Going a little higher layer:
SegmentProducer
andSegmentConsumer
can serve and retrieve segmented objects. It doesn't have fancy congestion control though.RdrMetadataProducer
enables version discovery of a published segmented object.PingServer
andPingClient
implement a simple reachability test.- These can serve as examples on how to write applications.
In C++ land there are some limitations due to memory management:
- If you allocate
Interest
,Data
, orNack
from aRegion
, be sure to test for allocation failure:if (!interest) { /* allocation failure */ return; }
. Otherwise, you may run into segmentation faults. - Neither
Face
norPacketHandler
keeps track of pending Interests. This means that your application may receive Data/Nack that you didn't express Interest for. You have to check in theprocessData
andprocessNack
override, and returnfalse
if the packet isn't for you so that it can go to the nextPacketHandler
. - Likewise, there's no notification about Interest timeout. If you need that, you'll have to keep the timer yourself.
- Received signed Interest or Data will have the signature stored in
Interest
orData
class. It is valid as long as theRegion
holding the packet buffer is alive. - You can can use
PacketHandler::send(interest.sign(key))
orPacketHandler::reply(data.sign(key))
to send signed Interest or Data, but the signature would not be stored inInterest
orData
class.
esp8266ndn in 2020
esp8266ndn has been upgraded to become a port of NDNph. Most NDN related features come directly from NDNph. What remains in esp8266ndn codebase are:
Transport
subclasses, including UDP, Ethernet, and Bluetooth Low Energy.- Crypto integrations.
- ESP32 thread-safe queue implementation.
queryFchService
to connect to global NDN testbed.UnixTime
service to perform time synchronization.
Integration with NDN-Lite has been removed, but in the future I may publish that as a separate library.
How to Get Started
NDNph repository on GitHub and esp8266ndn repository on GitHub have setup instructions in README. When installed in Arduino IDE, you can access esp8266ndn examples.
Thanks and good luck!