概述
文章目录
- 前言
- 一些前置知识
- 非负整数编码规则
- TLV编码
- data包和interest包的编码规则
- 正片:data包和interest包是如何执行tlv编码、解码过程
- 编码过程
- 解码函数、判断包的类型
- ??
前言
前几天,我们组打算加一种新的数据包类型,这就需要我们了解data包和interest包的编解码过程,从而仿照这一过程,加入我们的新的包的类型。
具体师姐给我的任务要求是:
节点收到一个包,一般是根据第一个Type字段判断包的类型,Type=5是兴趣包,Type=6是数据包,然后执行对应的TLV解码和转发操作。请找一下,这段“判断包类型”的代码在那个文件里?
【注意:本文的结论并不完整,只是整个编解码的一部分。不过最近我有其他优先级更高的事情要做,所以暂时没有时间来修改本文,所以暂时让本文就这样。等我有空了再来重新理清这部分内容。】
一些前置知识
为了让读者能够读懂本文,先介绍一些ndn里用的编码的一些前置知识。
非负整数编码规则
首先,ndn对于非负整数的编码规则是按照官网上的Non-Negative Integer Encoding规则进行编码1的。(推荐大家到官网去看,这里讲得并不详细)
具体来说,对于ndn中的一个非负整数(Non-Negative Integer),其按照非负整数编码(Non-Negative Integer Encoding)规则执行编码。详细的规则为:
- 如果这个数 ⩽ 252 leqslant 252 ⩽252 ,那么就用1字节表示它本身
- 如果这个数 ⩽ 2 16 leqslant 2^{16} ⩽216 ,那么用3字节表示它。其中首字节为 253 253 253 ,用于标志长度为 2 2 2 ,代表真正表示这个数的是后面 2 2 2 字节
- 如果这个数 ⩽ 2 32 leqslant 2^{32} ⩽232 ,那么用3字节表示它。其中首字节为 254 254 254 ,用于标志长度为 4 4 4 ,代表真正表示这个数的是后面 4 4 4 字节
- 如果这个数 ⩽ 2 64 leqslant 2^{64} ⩽264 ,那么用3字节表示它。其中首字节为 255 255 255 ,用于标志长度为 8 8 8 ,代表真正表示这个数的是后面 8 8 8 字节
举几个具体的例子,大家就懂了(如果还不懂,请怒骂博主sb,然后去看官网!)
0 ⇒ 0x00
127 ⇒ 0x7F
252 ⇒ 0xFC
253 ⇒ 0xFD00FD
256 ⇒ 0xFD0100
65535 ⇒ 0xFDFFFF
65536 ⇒ 0xFE00010000
其实这种编码也挺好理解的,作为一种变长码,既保证了短码字的利用率,也保证了长码字的可扩展性。
TLV编码
所谓的TLV编码,全名就是 Type(T)-Length(L)-Value(V)
编码。说白了,每个包都被切成 TLV
的格式,前面 Type
代表类型,给大家摘录一段 tlv.hpp
的代码就懂了
namespace tlv {
enum : uint32_t {
Invalid = 0,
Interest = 5,
Data = 6,
Name = 7,
GenericNameComponent = 8,
ImplicitSha256DigestComponent = 1,
ParametersSha256DigestComponent = 2,
CanBePrefix = 33,
MustBeFresh = 18,
... // 以下太长了,不再全部摘录,自己去 <ns-3 folder>/build/ns3/ndnSIM/ndn-cxx/encoding/tlv.hpp 看吧
};
}
比如 tlv::Interest = 5
的意思就是:你看到 Type
是5,那么就代表这个包是一个interest包。那么这个interest包有多长呢?这就得要看 Length(L)
了。其中 Type
和 Length(L)
都是按照非负整数编码规则编码的,解码环节的代码会按照 Length(L)
取出对应长度的 Value
。
注意:T和L都是按照非负整数编码规则编码的!
举个例子:假设
Type=256, Length=65535
,按照非负整数编码规则,这个TLV就应当是(0xFD0100)(0xFDFFFF)(xxxxxxxxxxxxxx)
,其中加括号只是为了方便读者理解,(xxxxxxxxxxxxxx)
代表65535字节长的包。
data包和interest包的编码规则
上面介绍了TLV编码规则,我们知道 tlv::data=5
是不是说一个长度为
256
256
256 的data包的内容就是 (0x05)(0xFD0100)(xxxxxxx)
,其中 xxxxxxx
只有数据?并不是,其实是 xxxxxxx
中包含了数据、签名等各种信息,每种信息都用 tlv
编码进行。或者跟具体地说,最外层的 tlv=(Type=Name)(Length)(V)
的 V
里面
咱们还是拿一具体的例子来说吧。比如data包,假设其中含有 Name+Content+SignatureInfo+SignatureValue
的信息。所以,实际上,这个data包里面真正包含的内容如下图所示:
因此,我们一定要注意一件事情——TLV编码的过程是反向进行的,TLV的解码是正向进行的,因为越里面的越应该先塞进去。
正片:data包和interest包是如何执行tlv编码、解码过程
有了前面的前置知识后,我们再来理解整个编解码过程就轻松多了。
编码过程
以下代码为在 ~/ndnSIM/ns-3/src/ndnSIM/ndn-cxx/ndn-cxx/data.cpp
的第46-84行。
template<encoding::Tag TAG>
size_t
Data::wireEncode(EncodingImpl<TAG>& encoder, bool wantUnsignedPortionOnly) const
{
// Data ::= DATA-TLV TLV-LENGTH
// Name
// MetaInfo?
// Content?
// SignatureInfo
// SignatureValue
size_t totalLength = 0;
// SignatureValue
if (!wantUnsignedPortionOnly) {
if (!m_signature) {
NDN_THROW(Error("Requested wire format, but Data has not been signed"));
}
totalLength += encoder.prependBlock(m_signature.getValue());
}
// SignatureInfo
totalLength += encoder.prependBlock(m_signature.getInfo());
// Content
totalLength += encoder.prependBlock(getContent());
// MetaInfo
totalLength += getMetaInfo().wireEncode(encoder);
// Name
totalLength += getName().wireEncode(encoder);
if (!wantUnsignedPortionOnly) {
totalLength += encoder.prependVarNumber(totalLength);
totalLength += encoder.prependVarNumber(tlv::Data);
}
return totalLength;
}
解码函数、判断包的类型
首先,回顾我们的问题
节点收到一个包,一般是根据第一个Type字段判断包的类型,Type=5是兴趣包,Type=6是数据包,然后执行对应的TLV解码和转发操作。请找一下,这段“判断包类型”的代码在那个文件里?
详见ndnSIM学习(九)——从consumer发兴趣包到producer返回data包的全过程,收到包判断是在 GenericLinkService::decodeNetPacket
函数。物理层收到包后
答案是:见 ~/ndnSIM/ns-3/src/ndnSIM/ndn-cxx/ndn-cxx/face.cpp
第273~307行的函数 Face::onReceiveElement
,其中第282行的 switch (netPacket.type())
就是“判断包类型”的代码——
void
Face::onReceiveElement(const Block& blockFromDaemon)
{
lp::Packet lpPacket(blockFromDaemon); // bare Interest/Data is a valid lp::Packet,
// no need to distinguish
Buffer::const_iterator begin, end;
std::tie(begin, end) = lpPacket.get<lp::FragmentField>();
Block netPacket(&*begin, std::distance(begin, end));
switch (netPacket.type()) {
case tlv::Interest: {
auto interest = make_shared<Interest>(netPacket);
if (lpPacket.has<lp::NackField>()) {
auto nack = make_shared<lp::Nack>(std::move(*interest));
nack->setHeader(lpPacket.get<lp::NackField>());
extractLpLocalFields(*nack, lpPacket);
NDN_LOG_DEBUG(">N " << nack->getInterest() << '~' << nack->getHeader().getReason());
m_impl->nackPendingInterests(*nack);
}
else {
extractLpLocalFields(*interest, lpPacket);
NDN_LOG_DEBUG(">I " << *interest);
m_impl->processIncomingInterest(std::move(interest));
}
break;
}
case tlv::Data: {
auto data = make_shared<Data>(netPacket);
extractLpLocalFields(*data, lpPacket);
NDN_LOG_DEBUG(">D " << data->getName());
m_impl->satisfyPendingInterests(*data);
break;
}
}
}
具体来说,以一个interest包为例。假设我们现在收到了一个 Block
类型的包,此时触发了函数 Face::onReceiveElement(const Block& blockFromDaemon)
。该函数会看这个 Block
的 type
,即 switch (netPacket.type())
如果 case tlv::Interest:
,那么就会执行 auto interest = make_shared<Interest>(netPacket)
,也就是说将这个包强制转化为 Interest
类型,这其实是调用了 Interest
的构造函数
Interest::Interest(const Block& wire)
{
wireDecode(wire);
}
同样的道理, case tlv::Data:
也为转化为 Data
类型。具体调用关系如下图所示——
这里由于时间关系,不跟大家细讲代码了,这里主要捋一捋整体框架性的思路。
Face::onReceiveElement
将Block
类型的内容,通过其type
解码判别出它是Interest
类还是Data
类,从而将Block
转化为对应的类型。- 转化为对应类型后,由对应的构造函数执行
wireDecode
函数。 - 每个类的
wireDecode
函数都配备有m_wire.parse()
函数,对Block
类的m_wire
进行参数解析。 - 参数解析方式基于前面的非负整数编码规则、TLV编码和data包和interest包的编码规则的前置知识,对于
Block
类型执行对应的解码。具体不想细讲了,前面已经讲得很细了。
这里的 readType
函数其实就是按照非负整数编码规则进行解码,这里我对于 readType
函数的一些坑点提示一下,以免大家重复踩坑。
其中 readType
函数调用了 readVarNumber
。
template<typename Iterator>
uint32_t
readType(Iterator& begin, Iterator end)
{
uint64_t type = readVarNumber(begin, end);
if (type == Invalid || type > std::numeric_limits<uint32_t>::max()) {
NDN_THROW(Error("Illegal TLV-TYPE " + to_string(type)));
}
return static_cast<uint32_t>(type);
}
我们以 ReadNumberSlow
函数为例,讲解一下大致流程。注意begin参数传的是引用,执行完后,begin会指向下一个字节,也就是说这个函数是有副作用的,一定要注意。
template<typename Iterator>
class ReadNumberSlow
{
public:
constexpr bool
operator()(size_t size, Iterator& begin, Iterator end, uint64_t& number) const noexcept
{
number = 0;
size_t count = 0;
for (; begin != end && count < size; ++begin, ++count) {
number = (number << 8) | *begin;
}
return count == size;
}
};
??
完蛋,忘了记录了,两天后全忘了,先把残存的遗骸保留下来吧
~/ndnSIM/ns-3/src/ndnSIM/NFD/daemon/fw/forwarder.cpp
的479-521行Forwarder::onOutgoingNack
函数的第519行调用了Face::sendNack
函数- ???
~/ndnSIM/ns-3/src/ndnSIM/NFD/daemon/face/link-service.cpp
的103-111行LinkService::receiveData
- ???
https://named-data.net/doc/NDN-packet-spec/current/tlv.html#non-negative-integer-encoding ↩︎
最后
以上就是搞怪缘分为你收集整理的ndnSIM学习(五)——data包和interest包的tlv编码、解码过程以及如何判断包的类型的全部内容,希望文章能够帮你解决ndnSIM学习(五)——data包和interest包的tlv编码、解码过程以及如何判断包的类型所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复