LWM2M开源协议栈——wakaama源代码分析
LWM2M开源协议栈——wakaama源代码分析
本⽂主要分析liblwm2m.h⽂件中⾮DEBUG状态下的代码,其他代码见后续⽂章
概述
wakaama呈现形式并不是⼀个C的库⽂件,⽽是以源代码的形式,直接和项⽬代码联合编译。编译模式分
为:LWM2M_SERVER_MODE、LWM2M_CLIENT_MODE、LWM2M_BOOTSTRAP_SERVER_MODE三种,分别对应此次编译产⽣的是服务器、客户端还是启动服务器。
分析顺序遵循,lwm2m.h⽂件从上到下的顺序,按照功能划分对函数原型和功能进⾏分析。
若有特殊情况,如上下代码关联,必须⼀起介绍的情况,会有特殊说明
如何编译
wakaama项⽬采⽤cmake作为项⽬构建⼯具,如何引⽤项⽬的,可以参照在/example下的各个项⽬的是如何编写的。需要注意的是,为了不破坏原有的项⽬结构,推荐采⽤外部构建的⽅式,即:
# 在wakaama之外新建你⾃⼰的项⽬⽬录,假设为project
cd project
# 注意替换变量为wakaama项⽬的所在位置,以编译server为例
# 将会在project⽬录下⽣成中间⽂件
cmake ${wakaama_base_dir}/example/server
make
为什么使用bootstrap?# 产⽣的⼆进制⽂件名,可以参看example/的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,⽤于存储各种协议中可能⽤到的数据。
数据结构和常量
LWM2M_TYPE_UNDEFINED = 0,
LWM2M_TYPE_OBJECT,
LWM2M_TYPE_OBJECT_INSTANCE,
LWM2M_TYPE_MULTIPLE_RESOURCE,
LWM2M_TYPE_STRING,
LWM2M_TYPE_OPAQUE,
LWM2M_TYPE_INTEGER,
LWM2M_TYPE_FLOAT,
LWM2M_TYPE_BOOLEAN,
LWM2M_TYPE_OBJECT_LINK
} lwm2m_data_type_t;
typedef struct _lwm2m_data_t lwm2m_data_t;
struct _lwm2m_data_t
{
lwm2m_data_type_t type;
uint16_t id;
union
{
bool asBoolean;
int64_t asInteger;
double asFloat;
struct
{
size_t length;
uint8_t * buffer;
} asBuffer;
struct
{
size_t count;
lwm2m_data_t * array;
} asChildren;
struct
{
uint16_t objectId;
uint16_t objectInstanceId;
} asObjLink;
} value;
};
数据类型与成员的对应关系
LWM2M_TYPE_OBJECT,LWM2M_TYPE_OBJECT_INSTANCE, LWM2M_TYPE_MULTIPLE_RESOURCE:value.asChildren
LWM2M_TYPE_STRING, LWM2M_TYPE_OPAQUE: value.asBuffer
LWM2M_TYPE_INTEGER,LWM2M_TYPE_TIME: value.asInteger
LWM2M_TYPE_FLOAT: value.asFloat
LWM2M_TYPE_BOOLEAN: value.asBoolean
关于OPAQUE
所谓OPAQUE即是指不透明数据类型,这种数据类型是指对⽤户完全隐藏了内部细节的数据结构。⽐如UNIX系统中的pid_t就是⼀种opaque数据类型,虽然它通常被实现为⼀个long int类型,但是系统设计者并不希望⽤户了解其设计细节,⽤户只需了解如何使⽤它即可。在LWM2M中,你可以将opaque理解为约定了特殊结构的buffer,只需要知道它的使⽤⽅式即可。
数据操作API
LWM2M_CONTENT_TEXT = 0, // Also used as undefined
LWM2M_CONTENT_LINK = 40,
LWM2M_CONTENT_OPAQUE = 42,
LWM2M_CONTENT_TLV = 11542,
LWM2M_CONTENT_JSON = 11543
} lwm2m_media_type_t;
lwm2m_data_t * lwm2m_data_new(int size);
int lwm2m_data_parse(lwm2m_uri_t * uriP, uint8_t * buffer, size_t bufferLen, lwm2m_media_type_t format, lwm2m_data_t ** dataP);
size_t lwm2m_data_serialize(lwm2m_uri_t * uriP, int size, lwm2m_data_t * dataP, lwm2m_media_type_t * formatP, uint8_t ** bufferP);
void lwm2m_data_free(int size, lwm2m_data_t * dataP);
void lwm2m_data_encode_string(const char * string, lwm2m_data_t * dataP);
void lwm2m_data_encode_nstring(const char * string, size_t length, lwm2m_data_t * dataP);
void lwm2m_data_encode_opaque(uint8_t * buffer, size_t length, lwm2m_data_t * dataP);
void lwm2m_data_encode_int(int64_t value, lwm2m_data_t * dataP);
int lwm2m_data_decode_int(const lwm2m_data_t * dataP, int64_t * valueP);
void lwm2m_data_encode_float(double value, lwm2m_data_t * dataP);
int lwm2m_data_decode_float(const lwm2m_data_t * dataP, double * valueP);
void lwm2m_data_encode_bool(bool value, lwm2m_data_t * dataP);
int lwm2m_data_decode_bool(const lwm2m_data_t * dataP, bool * valueP);
void lwm2m_data_encode_objlink(uint16_t objectId, uint16_t objectInstanceId, lwm2m_data_t * dataP);
void lwm2m_data_encode_instances(lwm2m_data_t * subDataP, size_t count, lwm2m_data_t * dataP);
void lwm2m_data_include(lwm2m_data_t * subDataP, size_t count, lwm2m_data_t * dataP);
#define LWM2M_TLV_HEADER_MAX_LENGTH 6
int lwm2m_decode_TLV(const uint8_t * buffer, size_t buffer_len, lwm2m_data_type_t * oType, uint16_t * oID, size_t * oDataIndex, size_t * oDataLen); API说明
函数名函数作⽤
lwm2m_data_parse将buffer中的数据根据formatP转化并dataP
lwm2m_data_serialize将dataP中的数据根据formatP序列化并填⼊bufferP
encode类函数将特定数据类型的数据编码并填⼊dataP
decode类函数从dataP中以特定数据类型提取数据并填⼊对应指针位置
关于TLV
所谓的TLV,实际上是⼀种三元组编码格式,为了解决发送⼆进制数据时,由于协议更新导致的对应字段意义变化的问题。举例来说,有如
下结构体规定了⼀种协议:
struct proto_t {
unsigned int version;
unsigned int name[50];
}
我们在通信时,可以直接将这个结构体的buffer进⾏发送,接收端接收到buffer后,将其转化为对应的结构体。
但是,当协议进⾏更新时:
struct proto_new_t {
unsigned int version;
unsigned int age;
unsigned int name[100];
char address[100];
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论