LWM2M开源协议栈——wakaama源代码分析
本文主要分析
liblwm2m.h
文件中非DEBUG状态下的代码,其他代码见后续文章
概述
wakaama呈现形式并不是一个C的库文件,而是以源代码的形式,直接和项目代码联合编译。编译模式分为:LWM2M_SERVER_MODE
、LWM2M_CLIENT_MODE
、LWM2M_BOOTSTRAP_SERVER_MODE
三种,分别对应此次编译产生的是服务器、客户端还是启动服务器。
分析顺序遵循,lwm2m.h
文件从上到下的顺序,按照功能划分对函数原型和功能进行分析。
若有特殊情况,如上下代码关联,必须一起介绍的情况,会有特殊说明
如何编译
wakaama
项目采用cmake
作为项目构建工具,如何引用项目的CMakeLists.txt
,可以参照在/example
下的各个项目的CMakeLists.txt
是如何编写的。需要注意的是,为了不破坏原有的项目结构,推荐采用外部构建的方式,即:
# 在wakaama之外新建你自己的项目目录,假设为project
cd project
# 注意替换变量为wakaama项目的所在位置,以编译server为例
# 将会在project目录下生成中间文件
cmake ${wakaama_base_dir}/example/server
make
# 产生的二进制文件名,可以参看example/server/CMakeLists.txt的PROJECT指令的参数
./lwm2mserver
你自己的项目可以参看例子的写法。
内存管理类API
void * lwm2m_malloc(size_t s);
void * lwm2m_free(void * p);
char * lwm2m_strdup(const char * str);
int lwm2m_strncmp(const char * s1, const char * s2, size_t n);
没有需要特别说明的函数,跟UNIX标准函数工作方式类似。
第三个函数作用是产生一个能放入str
的内存空间,并将str
的内容复制(duplicate)其中。
时间类API
time_t lwm2m_gettime(void);
返回距离上一次调用该函数时所流逝(elapse)的时间。根据POSIX规范,time_t
是一个有符号整数。错误时返回负数。
辅助功能类API
typedef struct _lwm2m_list_t
{
struct _lwm2m_list_t * next;
uint16_t id;
} lwm2m_list_t;
lwm2m_list_t * lwm2m_list_add(lwm2m_list_t * head, lwm2m_list_t * node);
lwm2m_list_t * lwm2m_list_find(lwm2m_list_t * head, uint16_t id);
lwm2m_list_t * lwm2m_list_remove(lwm2m_list_t * head, uint16_t id, lwm2m_list_t ** nodeP);
uint16_t lwm2m_list_newId(lwm2m_list_t * head);
void lwm2m_list_free(lwm2m_list_t * head);
#define LWM2M_LIST_ADD(H,N) lwm2m_list_add((lwm2m_list_t *)H, (lwm2m_list_t *)N);
#define LWM2M_LIST_RM(H,I,N) lwm2m_list_remove((lwm2m_list_t *)H, I, (lwm2m_list_t **)N);
#define LWM2M_LIST_FIND(H,I) lwm2m_list_find((lwm2m_list_t *)H, I)
#define LWM2M_LIST_FREE(H) lwm2m_list_free((lwm2m_list_t *)H)
提供了链表操作及其简化写法的宏。链表操作使用方式顾名思义。
lwm2m_list_newId
函数返回指定链表内未被使用的最小id。
lwm2m_list_free
函数工作方式是:仅对每个节点调用lwm2m_free
。
URI类API
LWM2M实体的抽象表示
对于LWM2M实体(比如一个支持LWM2M协议的设备),可访问服务被抽象为一个一个对象,每一个对象局有三种层次,分别是:Object,Object-Instance,Resource。举例来说,一个LWM2M实体上包含若干提供不同功能的对象(比如说若干种不同的传感器),而每一种功能有可能由多个对象实例提供(比如多个温度传感器,都提供温度读取的功能),这些对象实例实际所能完成的功能被称为资源。(比如温度传感器提供的数据,摄像机拍摄的影像等)。每一个层次在对应的层级上有着独立的ID,分别称为Object ID,Object Instance ID,Resource ID。OMA定义了一些标准的ID,例如:Object ID中的LWM2M_SECURITY_OBJECT_ID
为0,这一对象用于为节点间的通信提供安全功能。 而security object
对象中包含一些标准化的资源,比如,LWM2M_PUBLIC_KEY_ID
标识了security object
中的公钥资源。Object Instance ID主要用于唯一标识不同的对象实例,一般来说是在设备启动和对象实例化的时候,动态分配(依次从0增长,每一种对象有着不同的实例ID序列)的。
通过URI访问LWM2M实体
在LWM2M中,合法的URI应当像如下格式(注意,不能以/
结尾):
/<object id>[/<object instance id>][/<resource id>]
# legal example
/0
/0/0
/0/1
/0/1/1
URI分别标识了访问资源的object id、object instance id、resource id,后两个id是可选的。
在源代码中体现
#define LWM2M_MAX_ID ((uint16_t)0xFFFF)
#define LWM2M_URI_FLAG_OBJECT_ID (uint8_t)0x04
#define LWM2M_URI_FLAG_INSTANCE_ID (uint8_t)0x02
#define LWM2M_URI_FLAG_RESOURCE_ID (uint8_t)0x01
#define LWM2M_URI_IS_SET_INSTANCE(uri) (((uri)->flag & LWM2M_URI_FLAG_INSTANCE_ID) != 0)
#define LWM2M_URI_IS_SET_RESOURCE(uri) (((uri)->flag & LWM2M_URI_FLAG_RESOURCE_ID) != 0)
typedef struct
{
uint8_t flag; // indicates which segments are set
uint16_t objectId;
uint16_t instanceId;
uint16_t resourceId;
} lwm2m_uri_t;
#define LWM2M_STRING_ID_MAX_LEN 6
// Parse an URI in LWM2M format and fill the lwm2m_uri_t.
// Return the number of characters read from buffer or 0 in case of error.
// Valid URIs: /1, /1/, /1/2, /1/2/, /1/2/3
// Invalid URIs: /, //, //2, /1//, /1//3, /1/2/3/, /1/2/3/4
int lwm2m_stringToUri(const char * buffer, size_t buffer_len, lwm2m_uri_t * uriP);
lwm2m.h
通过lwm2m_stringToUri
函数,来将URI字符串转化为lwm2m_uri_t
结构。我们可以通过两个预定义的宏LWM2M_URI_IS_SET_INSTANCE
和LWM2M_URI_IS_SET_RESOURCE
来判断URI中后两个ID是否被设置。
常量宏
常量宏定义大约在源代码的135行左右,定义了CoAP Error Code
、标准对象ID、一部分标准资源的ID。
LWM2M数据类API
概述
LWM2M协议定义一种标准的数据类型lwm2m_data_t
,用于存储各种协议中可能用到的数据。
数据结构和常量
typedef enum
{
LWM2M_TYPE_UNDEFINED = 0,
LWM2M_TYPE_OBJECT,
LWM2M_TYPE_OBJECT_INSTANCE,
LWM2M_TYPE_MULTIPLE_RESOURCE,
LWM2M_TYPE_STRING,
LWM2