[嵌入式开发模块]Coap开源库libnyoci 使用详解
这些天花了老长时间研究libnyoci这个库了,主要是文档不全,就给了几个简单的示例。只能依靠接口文件里的那点注释结合着示例程序翻着源码一点点学习。这里把学习的结果发出来给大家参考,希望能帮助大家节省些时间。
注:学习这篇之前如果能先学完CoAP协议定义(RFC7252)的话会很有帮助。
TODO:
学习并补全observe相关接口的使用
学习并补全noderouter插件相关知识
文章目录
- 1 平台移植
- 2 libnyoci实例
- 2.1 实例创建
- 2.2 实例释放
- 3 网络相关设置
- 4 驱动运行
- 5 基本概念
- 6 处理进入的请求
- 6.1 设置回调handler
- 6.2 inbound处理
- 6.2.1 检查URI路径
- 6.2.2 检查是否支持请求的方法
- 6.2.3 检查选项
- 6.2.4 检查payload
- 6.3 outbound答复
- 6.3.1 开始答复及设置答复码
- 6.3.2 添加选项
- 6.3.3 添加内容
- 6.3.4 发送答复
- 6.4 资源发现接口
- 7 请求别处的资源
- 7.1 会话
- 7.1.1 创建会话
- 7.1.2 启动会话
- 7.1.3 终止会话
- 7.2 重传请求-回调函数
- 7.2.1 开始构造请求
- 7.2.2 设置目标URI
- 7.2.3 添加其他选项
- 7.2.4 发送请求包
- 7.3 答复-回调函数
- 附录:常用接口速查
- libnyoci.h
- coap.h
- nyoci-transaction.h
- nyoci-observable.h
- url-helpers.h
1 平台移植
为了使用libnyoci,必须先根据自己的平台和网络栈进行相应的移植和配置。
详情请移步:
https://blog.csdn.net/lin\_strong/article/details/96321759
2 libnyoci实例
libnyoci支持多实例,当然你其实可以通过设置将其设为单例模式,当为单例模式时,self参数失效,内部会自动设置为唯一的那个单例。
所有带有 nyoci_t self 参数的函数,就是让你在self参数传入你要操作的对应实例。
2.1 实例创建
不管是不是单例模式,使用libnyoci的第一步都是调用以下函数创建并初始化一个实例:
nyoci_t _CoapInstance;
_CoapInstance= nyoci_create();
if(!_CoapInstance){
// error
}
如果是多例模式,则需保留引用,在每次调用有self参数的函数时传入。
tip:建议单例模式时也这么处理,保持程序一致性。
2.2 实例释放
使用下面函数释放一个实例,一般用不到:
nyoci_release(_CoapInstance);
3 网络相关设置
当创建好实例后。在初始化好各项配置和回调函数之后(或之前,这无所谓),实际让libnyoci开始干活之前。你需要先保证网络的畅通,并设置网络相关参数,具体调用的设置函数和平台有关,在我的环境中我只调用了这两句:
nyoci_plat_set_socketnumber(_CoapInstance, COAP_SOCKET);
nyoci_plat_bind_to_port(_CoapInstance, NYOCI_SESSION_TYPE_UDP, COAP_DEFAULT_PORT);
4 驱动运行
为了libnyoci能不断运作,需要有进程不断地调用nyoci_plat_process,在这个函数中
以下是在多Task环境中,单独使用一个Task专门驱动libnyoci运行的主要语句:
while (1) {
MyOS_DlyHMSM(0, 0, 0, 30);
nyoci_plat_process(_CoapInstance);
}
裸奔环境中自己找机会不断调用它。
注意,libnyoci虽然是异步的,但它的方法都是线程不安全的。
5 基本概念
会话分为inbound和outbound部分,顾名思义,inbound部分对应外面传进来的包,一般是请求包;outbound部分则是要发出去的包,可能是对请求的答复,也可能是你要请求别的资源而主动使用的。每个实例对应inbound和outbound各有一个缓冲区。
在libnyoci.h中分别给了inbound和outbound各自的接口
6 处理进入的请求
6.1 设置回调handler
libnyoci服务器的处理请求的最基本模式就是通过设置handler函数,每当收到一个需要用户处理的请求时,libnyoci就会回调这个handler。用户在handler中进行必要的处理,可能会进行答复,也可能直接丢弃包。
static nyoci_status_t request_handler(void* context){
// 处理inbound包
}
……
nyoci_set_default_request_handler(_CoapInstance, &request_handler, NULL);
我们知道CoAP协议的应答有很多必要的步骤:设置一致的消息ID、Token;根据是CON还是NON请求设置对应的答复类型等。这些在调用回调函数前,libnyoci就已经帮我们处理好了,不需要我们操心,我们要做的就是执行请求的内容,决定答复码,添加需要的选项以及payload,或者直接选择不答复。
如果不在handler中显式的发送答复(或者选择直接放弃答复)的话,libnyoci会根据返回的值自动生成对应的答复,规则好像是对于CON则生成对应ACK答复,对于NON则直接不答复。所以,如果想要对NON也进行答复的话,则需要显式的发送答复,对NON的答复默认是NON类型。
接下来我们来讲下request_handler中的主要处理步骤。
6.2 inbound处理
6.2.1 检查URI路径
每个资源都有自己的URI,首先我们重组出资源的URI,如果没有对应资源的话就答复经典的404。
按理来说应该是一个个Uri-Path选项去检查,跟查找文件一样去搜索。但是libnyoci提供了一个很方便的函数nyoci_inbound_get_path,于是我们可以写成这样:
char buf[30];
……
if(nyoci_inbound_get_path(buf, sizeof(buf), 0) != buf){
// 路径转换失败
return NYOCI_STATUS_NOT_FOUND;
}
// 假设我们有个资源的路径为data,也可以是多层的如data/abc
if(strcmp(buf, "data") != 0){
// 没有给定的资源
return NYOCI_STATUS_NOT_FOUND;
}
注意,那两句
return NYOCI_STATUS_NOT_FOUND;
也可以改成
// 发送答复包的简易接口,第一个参数为答复码,第二个参数为内容,没有的话就传NULL
return nyoci_outbound_quick_response(COAP_RESULT_404_NOT_FOUND, NULL);
这样的效果是,对于CON消息类型的请求,两句没有差别。但对于NON消息类型,上面那句会不进行任何答复,效果上就是忽略了这个请求;而显式的答复的话就会发送一个NON消息类型的独立答复。
像这样:后面对于答复错误的写法同理,不再赘述。
6.2.2 检查是否支持请求的方法
请求的方法就是GET、POST、PUT或DELETE。这个是要根据资源的用途自己设计的。
比如如果只支持GET方法的话,可以这么写:
if(nyoci_inbound_get_code() != COAP_METHOD_GET) {
return NYOCI_STATUS_NOT_ALLOWED;
}
通过返回NYOCI_STATUS_NOT_ALLOWED,如果是CON消息,libnyoci会自动为你生成答复码为4.05(Method Not Allowed)的ACK包进行答复。
6.2.3 检查选项
CoAP包中可能会带有各种各样的选项。实际上前面的路径值就是从Uri-Path选项中恢复出来的
选项一定是按照编号从低到高排列的,有些选项可以出现多次(Repeatable),所以同一选项出现多次时一定是相邻的。有些选项是关键选项,也就是说你要是不理解这个选项而他又存在的话应该要拒绝请求。当然实际怎么处理各个选项是你代码来决定的,你甚至可以忽略所有的选项。
下图用于一览几乎所有的选项。不同的资源对不同的请求方法可能有不同的选项处理要求。因此可能你需要做一个表,为不同的 资源+方法 组合跳转不同的handler。
可以使用nyoci_inbound_next_option和/或nyoci_inbound_peek_option来遍历选项,它们的第一个参数(示例中为value)返回指向选项值的指针,第二个参数(示例中为value_len)返回选项值的长度;对于字符串类型的选项,value_len就是字符串的长度,value指向字符串的头;对于无符号整型值的选项,我们可以通过coap_decode_uint32来获得其值。
代码示例:
// 因为前面转换路径时移动了选项指针,所以现在重置下
nyoci_inbound_reset_next_option();
{
coap_option_key_t key;
const uint8_t* value;
coap_size_t value_len;
uint32_t ct;
// 扫描所有选项
while ((key = nyoci_inbound_next_option(&value, &value_len)) != COAP_OPTION_INVALID) {
switch(key){
case COAP_OPTION_URI_PATH: // 已经处理过了,跳过
// 暂时跳过,因为我们不使用虚拟主机
case COAP_OPTION_URI_HOST:
case COAP_OPTION_URI_PORT:
break;
case COAP_OPTION_CONTENT_TYPE:
// 如果比如POST或PUT支持多种格式的话,请求者可以通过这个选项告知内容的格式
printf("Content type: %s\r\n", coap_content_type_to_cstr(coap_decode_uint32(value, value_len)));
break;
case COAP_OPTION_URI_QUERY:
// 即URL中 ?XXXXX&XXXX 的XXXX部分内容,可以携带各种参数,按需自己解析
printf("Query: %.*s\r\n", value_len, value);
break;
case COAP_OPTION_ACCEPT:
ct = coap_decode_uint32(value, value_len);
// 如果无法支持要求的格式,答复4.06
if(dontSupport(ct))
return nyoci_outbound_quick_response(COAP_RESULT_406_NOT_ACCEPTABLE);
break;
default:
// 如果不识得的选项是关键选项,Bad option错误。
if(COAP_OPTION_IS_CRITICAL(key)) {
return NYOCI_STATUS_BAD_OPTION;
}
break;
}
}
}
6.2.4 检查payload
可能GET请求不会携带payload。但PUT和POST请求一般会随带一个payload,用于提交或更新。这时我们就需要提取其中的内容进行处理,示例如下:
{
const char* content_ptr;
coap_size_t content_len;
content_ptr = nyoci_inbound_get_content_ptr();
content_len = nyoci_inbound_get_content_len();
printf("Payload: %.*s\r\n", content_len, content_ptr);
}
而获得payload格式的方法已经在上一节说明了。具体怎么处理那是应用自己定义的事情。
6.3 outbound答复
检查完请求包后,如果没有发现什么问题,且需要进行响应的话那就要考虑怎么构建答复包的问题了,我们通过outbound的各种接口来构建答复。
对于最简单的只答复一个答复码的答复包,就调用之前提到的那个nyoci_outbound_quick_response接口就行了。
对于稍微复杂点的答复,我们就需要按照标准步骤构建答复包。
6.3.1 开始答复及设置答复码
首先,为了开始一个答复,先要调用nyoci_outbound_begin_response表明开始答复,接口中直接传入答复码:
比如要答复一个带有内容的答复的话,就会像这样:
nyoci_outbound_begin_response(COAP_RESULT_205_CONTENT);
如果需要独立设置/修改答复码,可以调用
nyoci_status_t nyoci_outbound_set_code(coap_code_t code);
6.3.2 添加选项
设置好答复码后,一般就要添加选项了,libnyoci提供了以下两个接口来给outbound包添加选项
//! 往outbound包中增加给定的选项
/*! 如果`value`是一个C字符串,简单的传递NYOCI_CSTR_LEN为`len`的值以避免调用`strlen()`
** 注意: 按数字顺序添加选项比随机顺序添加选项快的多。只要做得到,就按递增的顺序添加选项。*/
nyoci_status_t nyoci_outbound_add_option(
coap_option_key_t key,
const char* value,
coap_size_t len
);
//! 往outbound包中增加给定的一个值为无符号整型的选项
nyoci_status_t nyoci_outbound_add_option_uint(coap_option_key_t key, uint32_t value);
至于添加什么选项就看自己的需要了,比如我们一般要添加后面payload的文本类型:
nyoci_outbound_add_option_uint(
COAP_OPTION_CONTENT_TYPE,
COAP_CONTENT_TYPE_TEXT_PLAIN
);
6.3.3 添加内容
在添加完选项后(必须要添加完后),如果还需要添加payload的话,可以按如下操作(假设要答复的内容在content_ptr指向的长度为content_len的缓冲区中):
{
char *p;
coap_size_t max_len;
p = nyoci_outbound_get_content_ptr(&max_len);
if(content_len > max_len){
// 放不下时,对应的处理
}
// 往payload中写入内容
memcpy(p, content_ptr, content_len);
// 表明payload长度
nyoci_outbound_set_content_len(content_len);
}
或者用如下接口在尾部添加内容,这时不需要set_content_len,其会自动更新:
nyoci_outbound_append_content(content_ptr, content_len);
6.3.4 发送答复
很简单,一般也就是最后一句,然后就可以结束回调函数了,所以return它的结果:
return nyoci_outbound_send();
我简单的把POST过来的内容复制了一遍进行答复,于是就得到了如下结果:
6.4 资源发现接口
CoAP协议规定.well-known/core为众知的资源发现接口。客户端可以通过GET这个URI的资源,获得服务器拥有的资源的列表。
列表的格式由RFC6690定义,这是我的翻译:
https://blog.csdn.net/lin\_strong/article/details/103407291
一般会专门实现这个接口,使得客户端能够知道交互接口,比如我可以在handle中这样子简单实现它:
if(nyoci_inbound_get_path(buf, sizeof(buf), 0) != buf){
// 路径转换失败
return NYOCI_STATUS_NOT_FOUND;
}
if(strcmp(buf, ".well-known/core") == 0){
if (nyoci_inbound_get_code() != COAP_METHOD_GET)
return NYOCI_STATUS_NOT_ALLOWED;
nyoci_outbound_begin_response(COAP_RESULT_205_CONTENT);
nyoci_outbound_add_option_uint(COAP_OPTION_CONTENT_TYPE, COAP_CONTENT_TYPE_APPLICATION_LINK_FORMAT);
nyoci_outbound_append_cstr("</data/temp>;rt=\"temperature-c\";if=\"sensor\", </data/light>;rt=\"light-lux\";if=\"sensor\"");
return nyoci_outbound_send();
}else if……
else{
// 没有给定的资源
return NYOCI_STATUS_NOT_FOUND;
}
这样上位机就能通过返回的CoRE资源自动生成资源列表,选取需要的资源进行交互等。
7 请求别处的资源
我们以Get请求为例,示例如何使用libnyoci请求别处的资源。
7.1 会话
请求别处的资源时,libnyoci使用会话的概念,主要接口在nyoci-transaction.h中。
7.1.1 创建会话
创建会话的接口如下:
//! 初始化给定的transaction对象。
/* transaction 如果为NULL,则会自动分配一个对象给你,否则,直接初始化你给的这个
** flags 见NYOCI_TRANSACTION_XXXX,位模式
** requestResend 在这个回调函数中进行outbound发送请求
** responseHandler 在这个回调函数中答复
** context 会传递给两个回调函数的参数
*/
NYOCI_API_EXTERN nyoci_transaction_t nyoci_transaction_init(
nyoci_transaction_t transaction,
int flags,
nyoci_inbound_resend_func requestResend,
nyoci_response_handler_func responseHandler,
void* context
);
我们知道,一个完整的请求需要构造请求包并进行发送,可能还需要多次重试,然后得到答复后还需要处理答复。由于CoAP的包构造十分复杂,libnyoci将一个会话的这两个步骤抽象到两个回调函数中。在requestResend中你需要构造outbound包并进行发送,libnyoci每次想要重发请求时都会调用这个回调函数,因此你可以在其中比如做个重试计数啥的功能;而收到答复或者发生一些事件时,nyoci实例会回调responseHandler以要求你进行处理。
代码示例:
static struct nyoci_transaction_s transaction;
static nyoci_status_t resend_get_request(void* context);
static nyoci_status_t response_handler(int statuscode, void* context);
……
nyoci_transaction_init(
&transaction,
NYOCI_TRANSACTION_ALWAYS_INVALIDATE,
resend_request,
response_handler,
NULL
);
7.1.2 启动会话
调用nyoci_transaction_begin成功后,这个会话就会受到nyoci实例的调度,开始工作:
nyoci_status_t status = 0;
……
status = nyoci_transaction_begin(_CoapInstance, &transaction, _timeout);
if(status != NYOCI_STATUS_OK) {
// error
printf("nyoci_begin_transaction_old() returned %d(%s).\n",status,nyoci_status_to_cstr(status));
}
_timeout指定了会话的超时时间,按需要设置。
7.1.3 终止会话
如果要终止进行中的会话,可以调用以下接口:
NYOCI_API_EXTERN nyoci_status_t nyoci_transaction_end(
nyoci_t self,
nyoci_transaction_t transaction
);
7.2 重传请求-回调函数
刚刚说了,在requestResend中你需要构造outbound包并进行发送。现在我们来具体说说整个步骤。有很多步骤和前一章中outbound处理
的内容相似,毕竟它就是一个outbound。
我们的重传请求-回调函数的基本框架长这个亚子:
static nyoci_status_t resend_request(void* context) {
nyoci_status_t status = NYOCI_STATUS_OK;
……
bail:
return status;
}
context参数传进来的值就是你init中的最后一个参数。
7.2.1 开始构造请求
这里我们用的接口和答复请求时的略有不同,使用的是nyoci_outbound_begin,而不是nyoci_outbound_begin_response,BTW,后者其实只是在前者外面再加了层壳:
// 这里示例设置outbound包为GET请求,CON消息;实际按自己需求设置
status = nyoci_outbound_begin(nyoci_get_current_instance(),COAP_METHOD_GET, COAP_TRANS_TYPE_CONFIRMABLE);
require_noerr(status,bail); // nyoci自带的语法糖,相当于 if(status != NYOCI_STATUS_OK) goto bail;
7.2.2 设置目标URI
首先我们需要调用如下接口:
status = nyoci_outbound_set_uri(URI, NYOCI_MSG_SKIP_AUTHORITY);
require_noerr(status,bail);
这个接口会根据URI中解析出来的信息自动设置采用的通讯方案、目标主机和端口、资源路径、query参数
这个是UDP上coap的URI格式:
这个是DTLS的coap的URI格式
以及
coap+tcp://……是使用TCP
coaps+tcp://……是使用TLS
这个前缀(不含://)叫scheme,指定了通讯的协议。如果不含它的话,默认使用UDP。
当然,使用的通讯协议需要平台的支持才行,需要你在移植的时候在nyoci_plat_set_session_type中将其实现。
一般要指定主机部分,port部分如果不指定的话则会使用协议默认端口。
如果flag没设置NYOCI_MSG_SKIP_AUTHORITY的话,接口会把解析出来的主机和端口号设置为option,我觉得这个flag可以传。
host和port会使用nyoci_plat_set_remote_hostname_and_port进行设置,而host又会依赖于nyoci_plat_lookup_hostname进行解析,这些都是平台移植文件中的函数,需要你在移植时进行实现。
如果不想要nyoci_outbound_set_uri调用nyoci_plat_set_remote_hostname_and_port进行设置目标IP和端口的话,可以传递flag—NYOCI_MSG_SKIP_DESTADDR给它,就能跳过这一步。
但是随后一定要记得自己设置目标端口和IP。
设下的path和query则一定会按照正常的转换规则转换为option。虽然定义了NYOCI_MSG_SKIP_PATH和NYOCI_MSG_SKIP_QUERY这两个flag。但是看代码,实际上并没有使用它们,当然如果你有需要也可以自己加进去。
7.2.3 添加其他选项
然而有一些东西是无法用URI来标识的,因此还需要自己再根据需要添加其他option,比如一般会加上content-type option。
status = nyoci_outbound_add_option_uint(COAP_OPTION_ACCEPT, COAP_CONTENT_TYPE_TEXT_PLAIN);
require_noerr(status,bail);
要注意的是,当然也可以不使用上述set_uri接口,完全手动构造好整个包并设置好目标IP之类的东西。
7.2.4 发送请求包
最后调用一下send就好。
status = nyoci_outbound_send();
switch (status) {
case NYOCI_STATUS_OK:
case NYOCI_STATUS_WAIT_FOR_SESSION:
case NYOCI_STATUS_WAIT_FOR_DNS:
break;
default:
check_noerr(status);
printf("nyoci_outbound_send() returned error %d(%s).\n",
status,
nyoci_status_to_cstr(status));
break;
}
7.3 答复-回调函数
当收到发出的请求的对应答复包时,或者在异步处理中发生任何错误时,responsehandler会被调用,你需要在其中进行对应处理,提取你需要的信息或处理故障。答复包是个inbound,所以当收到答复包时,和前面的inbound处理
部分几乎一样,因此我们不再赘述具体的inbound处理过程。
答复-回调函数的框架是这样子的:
static nyoci_status_t response_handler(int statuscode, void* context) {
const char* content = (const char*)nyoci_inbound_get_content_ptr();
coap_size_t content_length = nyoci_inbound_get_content_len();
……
return NYOCI_STATUS_OK;
}
context自然传递的就是init会话时传递的那个参数,而返回值其实并没有什么意义。我们需要理解下statuscode的处理方式。
对于有答复的请求,如果收到答复了,libnyoci就会使用那个答复的答复码来调用statuscode,值自然是大于0的,比如我要是GET一个资源成功的话,答复码就是COAP_RESULT_205_CONTENT。有些时候会有多次答复,比如观测一个资源的时候,这个时候这个答复回调函数就可能会被多次调用。
如果发生错误了,则会用对应的状态码(小于0)调用回调函数,这里整理几个常见的:
NYOCI_STATUS_TIMEOUT 当超过了你给定的超时时间还没有收到答复时(针对CON)
在内部调用你nyoci_plat_outbound_finish进行发送时,如返回的不是NYOCI_STATUS_OK,则会用返回的那个值调用回调函数
NYOCI_STATUS_TRANSACTION_INVALIDATED 一个会话将要失效。
……
注意:除非你在初始化会话时设置了NYOCI_TRANSACTION_ALWAYS_INVALIDATE,不然,对于那种一次性的会话(一个普通的请求,或者发生了什么异常而导致结束),答复-回调函数只会被只用对应的答复码或者错误码调用一次,然后就直接结束了。并不会再使用NYOCI_STATUS_TRANSACTION_INVALIDATED调用答复-回调函数一次。对于那种长期的会话,比如观测一个资源的会话,不设置这个flag可能会导致难以得知会话在什么时候终止了。 所以建议干脆就都设置这个flag,然后就通过 答复-回调函数被使用NYOCI_STATUS_TRANSACTION_INVALIDATED调用,作为会话结束的标志就行了。
GET请求的一个答复-回调函数简单示例如下:
static nyoci_status_t response_handler(int statuscode, void* context) {
const char* content = (const char*)nyoci_inbound_get_content_ptr();
coap_size_t content_length = nyoci_inbound_get_content_len();
const char* desc = "";
if (statuscode == NYOCI_STATUS_TRANSACTION_INVALIDATED) {
printf("Trans Invalidated\r\n");
_tranStatus = TRANSACTION_UNREADY;
} else if (statuscode == COAP_RESULT_205_CONTENT) {
printf("Got content: %.*s\r\n", content_length, content);
} else if (statuscode == NYOCI_STATUS_TIMEOUT){
printf("request timeout\r\n");
}else {
desc = (statuscode > 0)?coap_code_to_cstr(statuscode): nyoci_status_to_cstr(statuscode);
printf("ERROR: Got unexpected status code %d (%s)\r\n", statuscode, desc);
}
return NYOCI_STATUS_OK;
}
我们使用这个示例来请求上一章中写的CaAP服务器,成功时结果如下:
得不到答复而超时的结果如下:
好的,主要内容到这就讲解完了,还有很多其他东西可能后面会慢慢补全。
有什么建议或意见欢迎留言!
附录:常用接口速查
只给出自己认为常用的,而且自己理解了的接口
libnyoci.h
libnyoci.h里头定义了libnyoci最宏观的相关接口,包括实例的创建和释放、回调函数设置、inbound和outbound缓冲区的相关接口。
// 创建并分配一个libnyoci实例
nyoci_t nyoci_create(void);
// 释放一个libnyoci实例,关闭所有端口并结束所有的会话
void nyoci_release(nyoci_t self);
// 获取及设置对当前实例的引用,内部使用
nyoci_t nyoci_get_current_instance(void);
void nyoci_set_current_instance(nyoci_t x);
// 设置默认的请求handler
// 只要实例收到了一个非代理相关的请求,这个handler就会被回调。
// 如果收到的是CON消息并且你在回调函数中没有发出答复消息,就会自动基于返回值生成并发出答复包。
// context会在回调时被作为参数传递
void nyoci_set_default_request_handler(
nyoci_t self,
nyoci_request_handler_func request_handler,
void* context
);
// 得知在下次调用nyoci_plat_process()前最多你还有多久时间
nyoci_cms_t nyoci_get_timeout(nyoci_t self);
// 以下函数只可以用于回调函数中来检查当前inbound包,在回调函数外用它们会导致运行时错误
//! 返回指向当前inbound CoAP包的开头的指针
NYOCI_API_EXTERN const struct coap_header_s* nyoci_inbound_get_packet(void);
//! 返回inbound包的长度
NYOCI_API_EXTERN coap_size_t nyoci_inbound_get_packet_length(void);
//! 返回inbound包的码
#define nyoci_inbound_get_code() (nyoci_inbound_get_packet()->code)
//! 返回inbound包的消息ID
#define nyoci_inbound_get_msg_id() (nyoci_inbound_get_packet()->msg_id)
#define NYOCI_INBOUND_FLAG_DUPE (1<<0)
#define NYOCI_INBOUND_FLAG_MULTICAST (1<<1)
#define NYOCI_INBOUND_FLAG_FAKE (1<<2)
#define NYOCI_INBOUND_FLAG_HAS_OBSERVE (1<<3)
#define NYOCI_INBOUND_FLAG_LOCAL (1<<4)
//! 返回一个flags字节,标识了inbound包的状态,位模式,见(NYOCI_INBOUND_FLAG_XXXXX)
NYOCI_API_EXTERN uint16_t nyoci_inbound_get_flags(void);
//! 返回是否LibNyoci认为当前inbound包是一个欺骗包
#define nyoci_inbound_is_dupe() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_DUPE)==NYOCI_INBOUND_FLAG_DUPE)
//! 返回是否LibNyoci认为当前inbound包是假的(用来触发对订阅者的消息推送)
#define nyoci_inbound_is_fake() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_FAKE)==NYOCI_INBOUND_FLAG_FAKE)
//! 返回是否当前inbound包是一个多播包
#define nyoci_inbound_is_multicast() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_MULTICAST)==NYOCI_INBOUND_FLAG_MULTICAST)
//! 返回是否当前inbound包有一个observe选项
#define nyoci_inbound_has_observe() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_HAS_OBSERVE)==NYOCI_INBOUND_FLAG_HAS_OBSERVE)
//! 返回是否LibNyoci认为当前inbound包是本地的
#define nyoci_inbound_is_local() ((nyoci_inbound_get_flags()&NYOCI_INBOUND_FLAG_LOCAL)==NYOCI_INBOUND_FLAG_LOCAL)
//! 返回指向当前inbound包的内容开头的指针,保证是以NULL终止的
NYOCI_API_EXTERN const char* nyoci_inbound_get_content_ptr(void);
//! 返回当前inbound包的内容的长度
NYOCI_API_EXTERN coap_size_t nyoci_inbound_get_content_len(void);
//! 返回当前inbound包observe头部的值
NYOCI_API_EXTERN uint32_t nyoci_inbound_get_observe(void);
//! 返回当前inbound包的内容类型
NYOCI_API_EXTERN coap_content_type_t nyoci_inbound_get_content_type(void);
//! 提取头部中下一个(或说当前指向的那个)选项的值和类型,并移动到下一个
//! ptr传递选项指针和返回新的选项指针,len返回选项的长度
NYOCI_API_EXTERN coap_option_key_t nyoci_inbound_next_option(const uint8_t** ptr, coap_size_t* len);
//! 提取头部中下一个(或说当前指向的那个)选项的值和类型,但不移动到下一个
//! ptr传递选项指针和返回新的选项指针,len返回选项的长度
NYOCI_API_EXTERN coap_option_key_t nyoci_inbound_peek_option(const uint8_t** ptr, coap_size_t* len);
//! 重置选项指针为最开始的那个
NYOCI_API_EXTERN void nyoci_inbound_reset_next_option(void);
//! 将当前选项的值与指定的key和C字符串表示的值进行对比,相等则返回true,否则返回false
NYOCI_API_EXTERN bool nyoci_inbound_option_strequal(coap_option_key_t key, const char* str);
#define nyoci_inbound_option_strequal_const(key,const_str) \
nyoci_inbound_option_strequal(key,const_str)
#define NYOCI_GET_PATH_REMAINING (1<<0) // 翻译当前余下部分(因为选项指针可能不在头部),否则重新开始
#define NYOCI_GET_PATH_LEADING_SLASH (1<<1) // 在最开头加上'/'
#define NYOCI_GET_PATH_INCLUDE_QUERY (1<<2) // 包含Query部分
//! 获得inbound包的目标路径的字符串表示(即请求URI)
//! where是缓冲区,maxlen为缓冲区大小,flags见上
NYOCI_API_EXTERN char* nyoci_inbound_get_path(char* where, coap_size_t maxlen, uint8_t flags);
// 以下是拼凑outbound包的API,他们用于在回调函数中构建outbound CoAP消息
// 从回调函数外调用这些函数是运行时错误
//! 将outbound包设置为请求
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_begin(
nyoci_t self,
coap_code_t code,
coap_transaction_type_t tt
);
//! 设置outbound包为对当前inbound包的答复
//! 这个函数会自动地确保目标地址,消息ID和Token合理地设置
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_begin_response(coap_code_t code);
//! 修改当前outbound包的码
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_code(coap_code_t code);
NYOCI_API_EXTERN nyoci_status_t nyoci_set_remote_sockaddr_from_host_and_port(const char* addr_str, uint16_t toport);
//! 往outbound包中增加给定的选项
/*! 如果`value`是一个C字符串,简单的传递NYOCI_CSTR_LEN为`len`的值以避免调用`strlen()`
** 注意: 按数字顺序添加选项比随机顺序添加选项快的多。只要做得到,就按递增的顺序添加选项。*/
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_add_option(
coap_option_key_t key,
const char* value,
coap_size_t len
);
//! 往outbound包中增加给定的一个值为无符号整型的选项
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_add_option_uint(coap_option_key_t key, uint32_t value);
// 这4个flags用于nyoci_outbound_set_uri()
#define NYOCI_MSG_SKIP_DESTADDR (1<<0) // 不通过URI设置目标地址和端口
#define NYOCI_MSG_SKIP_AUTHORITY (1<<1) // 不设置Uri-Host和Uri-Port选项
#define NYOCI_MSG_SKIP_PATH (1<<2) // 不设置Uri-Path选项(暂时无用)
#define NYOCI_MSG_SKIP_QUERY (1<<3) // 不设置Uri-Query选项(暂时无用)
//! 设置outbound包的目标URI
//! 如果目标URL不能直接可达,并且定义了代理URL,这个函数会自动使用代理
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_uri(const char* uri, char flags);
//! 返回指向outbound包内容部分的指针
/*! 如果需要添加内容到outbound消息中,调用这个函数然后写你的内容到返回的指针指向的位置。
** 不要写比返回的`max_len`更多的字节。
**
** 在写完你的数据后(或之前,这无关紧要),使用nyoci_outbound_set_content_len()来表明
** 内容的长度。
**
** 警告:在调用以下函数后绝对不能再添加任何选项,否则内容就没有了。先添加完所有选项!*/
NYOCI_API_EXTERN char* nyoci_outbound_get_content_ptr(
coap_size_t* max_len //^< [出参] 最大的内容长度
);
// 返回outbound缓冲区还剩多少字节空间
NYOCI_API_EXTERN coap_size_t nyoci_outbound_get_space_remaining(void);
//! 设置内容的实际长度。在nyoci_outbound_get_content_ptr()后调用它。
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_content_len(coap_size_t len);
//! 附加给定数据到包的末尾(也就是内容的末尾),这个函数会自动更新内容的长度
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_append_content(const char* value, coap_size_t len);
//! 附加给定C字符串到包的末尾,这个函数会自动更新内容的长度
#define nyoci_outbound_append_cstr(cstr) nyoci_outbound_append_content(cstr, NYOCI_CSTR_LEN)
#if !NYOCI_AVOID_PRINTF
//! 按printf风格写outbound消息的内容
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_append_content_formatted(const char* fmt, ...);
#define nyoci_outbound_append_content_formatted_const \
nyoci_outbound_append_content_formatted
#endif
//! 发送outbound包
/*! 在调用这个函数后,你这个回调函数的事情就做完了。后面就别调其他nyoci_outbound_*函数了。
** 每个回调函数中应该只发送一个outbound包 */
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_send(void);
//! 丢弃outbound包(不进行答复)
NYOCI_API_EXTERN void nyoci_outbound_drop(void);
//! 重置outbound包
NYOCI_API_EXTERN void nyoci_outbound_reset(void);
//! 设置outbound包的消息id
//! 注意:大部分情况下消息ID是自动处理的,一般你用不到这个函数
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_msg_id(coap_msg_id_t tid);
//! 设置outbound包的token
//! 注意:大部分情况下token是自动处理的,一般你用不到这个函数
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_set_token(const uint8_t *token, uint8_t token_length);
//! 封装了只设置答复码的答复过程,通知错误时很有用。
NYOCI_API_EXTERN nyoci_status_t nyoci_outbound_quick_response(coap_code_t code, const char* body);
coap.h
coap.h中定义了CoAP协议相关的常量定义,包解析/编码的相关函数以及一些常量转换函数。它是很底层的函数,为更高层的函数提供服务接口。
// COAP_RESULT_XXXX 与 HTTP_RESULT_CODE_XXXX间相互转换用函数
NYOCI_INTERNAL_EXTERN uint16_t coap_to_http_code(uint8_t x);
NYOCI_INTERNAL_EXTERN uint8_t http_to_coap_code(uint16_t x);
// 解析CoAP包的一个选项
// buffer:指向当前要解析的选项的指针
// key :返回解析出的选项编号
// value :返回指向解析出的选项的值的指针
// lenP : 返回选项的值的长度
// 返回 :指向下一个选项的指针
NYOCI_API_EXTERN uint8_t* coap_decode_option(
const uint8_t* buffer,
coap_option_key_t* key,
const uint8_t** value,
coap_size_t* lenP
);
NYOCI_API_EXTERN uint8_t* coap_encode_option(
uint8_t* buffer,
coap_option_key_t prev_key,
coap_option_key_t key,
const uint8_t* value,
coap_size_t len
);
//! 按正确的顺序插入一个选项
/*! 返回: 插入选项的字节数
*/
NYOCI_API_EXTERN coap_size_t coap_insert_option(
uint8_t* start_of_options,
uint8_t* end_of_options,
coap_option_key_t key,
const uint8_t* value,
coap_size_t len
);
// 返回选项值的格式是否是字符串
NYOCI_API_EXTERN bool coap_option_value_is_string(coap_option_key_t key);
NYOCI_API_EXTERN bool coap_option_strequal(const char* optionptr,const char* cstr);
#define coap_option_strequal_const(item,cstr) coap_option_strequal(item,cstr)
// The following functions are not recommended on embedded platforms.
// 返回内容类型对应的常量字符串
NYOCI_API_EXTERN const char* coap_content_type_to_cstr(coap_content_type_t ct);
// 返回选项对应的常量字符串,不同的for_response只会使COAP_OPTION_AUTHENTICATE的对应字符串稍微有点不同
NYOCI_API_EXTERN const char* coap_option_key_to_cstr(coap_option_key_t key, bool for_response);
// coap_content_type_to_cstr的反向转换函数
NYOCI_INTERNAL_EXTERN coap_content_type_t coap_content_type_from_cstr(const char* x);
// coap_option_key_to_cstr的反向转换函数
NYOCI_INTERNAL_EXTERN coap_option_key_t coap_option_key_from_cstr(const char* key);
// 返回HTTP_RESULT_CODE_XXXX对应的字符串
NYOCI_INTERNAL_EXTERN const char* http_code_to_cstr(int x);
// 返回COAP_RESULT_XXXX对应的字符串
NYOCI_API_EXTERN const char* coap_code_to_cstr(int x);
// 检查CoAP包的格式是否正确
NYOCI_INTERNAL_EXTERN bool coap_verify_packet(const char* packet,coap_size_t packet_size);
// CoAP包选项的值的格式为无符号整型时,用于返回无符号整型的值
// value和value_len由coap_decode_option或nyoci_inbound_XXXX_option获得
NYOCI_API_EXTERN uint32_t coap_decode_uint32(const uint8_t* value, uint8_t value_len);
nyoci-transaction.h
nyoci-transaction.h中定义了建立会话的相关接口,会话这个概念在libnyoci中用于请求别处CoAP服务器的服务,在发送请求前需要先初始化一个会话,会话可以是一次性的,也可以是持续观测的。
//! 会话答复的handler的定义,返回除NYOCI_STATUS_OK之外的几乎所有值都将导致handler失效
typedef nyoci_status_t (*nyoci_response_handler_func)(
int statuscode,
void* context
);
struct nyoci_transaction_s {
#if NYOCI_TRANSACTIONS_USE_BTREE
struct bt_item_s bt_item;
#else
struct ll_item_s ll_item;
#endif
nyoci_inbound_resend_func resendCallback;
nyoci_response_handler_func callback;
void* context;
// 这个expiration(有效期)字段在会话是或不是可观测会话的时候有不同的含义。
// 如果是不可观测的,有效期就是值会话多久后将超时的时间。
// 如果是可观测的,就是超过了最大到期时间,需要重启观测。
nyoci_timestamp_t expiration;
struct nyoci_timer_s timer;
coap_msg_id_t token;
coap_msg_id_t msg_id;
nyoci_sockaddr_t sockaddr_remote;
#if NYOCI_CONF_TRANS_ENABLE_OBSERVING
uint32_t last_observe;
#endif
#if NYOCI_CONF_TRANS_ENABLE_BLOCK2
uint32_t next_block2;
#endif
coap_code_t sent_code;
uint8_t flags;
uint8_t attemptCount:4, maxAttempts:4,
waiting_for_async_response:1,
should_dealloc:1,
active:1,
needs_to_close_observe:1,
multicast:1;
};
typedef struct nyoci_transaction_s* nyoci_transaction_t;
enum {
//! 当要失效会话时,一定要用NYOCI_STATUS_INVALIDATE调用回调函数
/*! 在一般的单播会话中,回调函数总是只会被调用一次,所以并不存在
* 不清楚会话是否已经被失效了的情况。然而,有一些特定的会话会多次
* 调用回调函数,所以有可能并不清楚会话是否已经被失效了。
* 如果用了这个flag,当会话要失效时,会话回调函数总是会被使用
* `NYOCI_STATUS_INVALIDATE`调用。*/
NYOCI_TRANSACTION_ALWAYS_INVALIDATE = (1 << 0),
NYOCI_TRANSACTION_OBSERVE = (1 << 1),
NYOCI_TRANSACTION_KEEPALIVE = (1 << 2), //!< 在观测间发送keep-alive包
NYOCI_TRANSACTION_NO_AUTO_END = (1 << 3),
NYOCI_TRANSACTION_BURST_UNICAST = (1 << 4), //!< 在每次重传时突然发送一堆单播包
NYOCI_TRANSACTION_BURST_MULTICAST = (1 << 5), //!< 在每次重传时突然发送一堆多播包
NYOCI_TRANSACTION_BURST = NYOCI_TRANSACTION_BURST_UNICAST|NYOCI_TRANSACTION_BURST_MULTICAST, //!< 在每次重传时突然发送一堆包
NYOCI_TRANSACTION_DELAY_START = (1 << 8),
};
//! 初始化给定的transaction对象。
/* transaction 如果为NULL,则会自动分配一个对象给你,否则,直接初始化你给的这个
** flags 见NYOCI_TRANSACTION_XXXX,位模式
** requestResend 在这个回调函数中进行outbound发送请求
** responseHandler 在这个回调函数中答复
** context 会传递给两个回调函数的参数
*/
NYOCI_API_EXTERN nyoci_transaction_t nyoci_transaction_init(
nyoci_transaction_t transaction,
int flags,
nyoci_inbound_resend_func requestResend,
nyoci_response_handler_func responseHandler,
void* context
);
NYOCI_API_EXTERN nyoci_status_t nyoci_transaction_begin(
nyoci_t self,
nyoci_transaction_t transaction,
nyoci_cms_t expiration
);
//! 结束会话
NYOCI_API_EXTERN nyoci_status_t nyoci_transaction_end(
nyoci_t self,
nyoci_transaction_t transaction
);
//! 强制会话重试/重传
NYOCI_API_EXTERN nyoci_status_t nyoci_transaction_tickle(
nyoci_t self,
nyoci_transaction_t transaction
);
//! 修改制定会话的消息ID
/*! 不要修改token。这是用于当你想要使用同个会话对象来处理一系列
** 消息请求和答复时。
** 比如,用于观测以及分块传输 */
NYOCI_API_EXTERN void nyoci_transaction_new_msg_id(
nyoci_t self,
nyoci_transaction_t handler,
coap_msg_id_t msg_id
);
nyoci-observable.h
创建和维护可观测资源的相关接口
//! 可观测上下文。
//! 这个结构体用于追踪-谁在观测这个资源。你想要多少就能有多少。
struct nyoci_observable_s {
#if !NYOCI_SINGLETON
nyoci_t interface;
#endif
// 应把以下成员当做私有的。
int8_t first_observer; //!^ always +1, zero is end of list
int8_t last_observer; //!^ always +1, zero is end of list
};
//! 使用给定的可观测内容触发所有的观测者的KEY
#define NYOCI_OBSERVABLE_BROADCAST_KEY (0xFF)
typedef struct nyoci_observable_s *nyoci_observable_t;
//! 使一个资源可观测的Hook
/*! 它必须在你“开始”构造outbound答复消息后,而还没有填充内容前被调用。
** 更准确地说就是:
**
** *在nyoci_outbound_begin()或nyoci_outbound_begin_response()后
** *在nyoci_outbound_get_content_ptr()、nyoci_outbound_append_content()、
** nyoci_outbound_send()等函数前。
**
** 你可以为`key`选择任何值,只要与你传递给nyoci_observable_trigger()来触发更新的那个一致就行。
*/
NYOCI_API_EXTERN nyoci_status_t nyoci_observable_update(
nyoci_observable_t context, //!< [入参] 指向可观测上下文的指针
uint8_t key //!< [入参] 这个资源的Key(比如与在trigger中使用的一致)
);
#define NYOCI_OBS_TRIGGER_FLAG_NO_INCREMENT (1<<0)
#define NYOCI_OBS_TRIGGER_FLAG_FORCE_CON (1<<1)
//! 触发一个可观测资源发送更新给它的观测者们。
/*!
** 你可以使用NYOCI_OBSERVABLE_BROADCAST_KEY以触发与这个可观测上下文相关的所有资源的更新。
*/
NYOCI_API_EXTERN nyoci_status_t nyoci_observable_trigger(
nyoci_observable_t context, //!< [入参] 指向可观测上下文的指针
uint8_t key, //!< [入参] 这个资源的Key(比如与在update中使用的一致)
uint8_t flags //!< [入参] 标志位
);
//! 让所有的可观测资源发送一个CON更新给它们的所有观测者。
/*!
** 偶尔使用它来清理下线的观测者非常有用,特别是当你的可观测资源更新地很不频繁时。
** 这个功能被拆分成独立地函数,而不是搞成自动地,这样你就可以在适当的时机调用它,
** 比如在定时唤醒时。
*/
NYOCI_API_EXTERN void nyoci_refresh_observers(nyoci_t interface, uint8_t flags);
//! 返回活跃的观测者的数量
NYOCI_API_EXTERN int nyoci_count_observers(nyoci_t interface);
//! 返回指定资源和key的观测值个数
/*!
** 你可以给参数key传递NYOCI_OBSERVABLE_BROADCAST_KEY来获得与这个上下文相关的观测者的个数。
*/
NYOCI_API_EXTERN int nyoci_observable_observer_count(
nyoci_observable_t context, //!< [入参] 指向可观测上下文的指针
uint8_t key //!< [入参] 这个资源的Key(比如与在update中使用的一致)
);
//! 移除指定资源和key的所有观测者
/*!
** 你可以给参数key传递NYOCI_OBSERVABLE_BROADCAST_KEY来清除与这个上下文相关的所有观测者。
*/
NYOCI_API_EXTERN int nyoci_observable_clear(
nyoci_observable_t context, //!< [入参] 指向可观测上下文的指针
uint8_t key //!< [入参] 这个资源的Key(比如与在update中使用的一致)
);
url-helpers.h
url辅助函数主要负责url字符串的编码和解码,url在传输时需要把那些非ASCII字符进行百分比编码,收到后可能显示前会进行解码,这个模块主要就负责这个事。
#define URL_HELPERS_MAX_URL_COMPONENTS (15)
#define MAX_URL_SIZE (256)
/*! 对给定字符串执行URL编码
** 返回:被编码的字符串的字节数
*/
NYOCI_INTERNAL_EXTERN size_t url_encode_cstr(
char *dest, //!< [入参] 指向目标C字符串的缓冲区的指针
const char* src, //!< [入参] 源字符串,必须是NULL结尾的。
size_t dest_max_size //!< [入参] 目标缓冲区的大小
);
//! 解码字符串
NYOCI_INTERNAL_EXTERN size_t url_decode_str(
char *dest,
size_t dest_max_size,
const char* src, //!< 长度由 `src_len`确定。
size_t src_len
);
/*! 对给定字符串执行URL解码
** 返回:被解码的字符串的字节数
*/
NYOCI_INTERNAL_EXTERN size_t url_decode_cstr(
char *dest,
const char* src, //!< 源字符串,必须是NULL结尾的。
size_t dest_max_size
);
// 原地解码字符串(所以你得保证这个字符串是可修改的)
NYOCI_INTERNAL_EXTERN void url_decode_cstr_inplace(char *str);
// 将字符串用双引号引起来
NYOCI_INTERNAL_EXTERN size_t quoted_cstr(
char *dest,
const char* src, //!< 源字符串,必须是NULL结尾的。
size_t dest_max_size
);
NYOCI_INTERNAL_EXTERN bool url_is_absolute(const char* url);
NYOCI_INTERNAL_EXTERN bool url_is_root(const char* url);
// 返回字符串中是否包含':'
NYOCI_INTERNAL_EXTERN bool string_contains_colons(const char* str);
还没有评论,来说两句吧...