一、简介
OpenWrt路由操作系统的框架基础软件有很多,大部分是通用的软件模块,如 dhcp 、dnsmasq、iproute、cmwp、vpn、ipsec等等;OpenWrt还集成部分具有专属特征软件模块,也是OpenWRT系统核心框架软件组件,从此篇开始分析 《OpenWrt系统框架基础软件模块》系列文章。
OpenWrt 核心软件:procd、uci、libubox、ubus、ubox、luci、netifd 软件组件内容,此部分软件模块是构成OpenWrt框架基础软件。
因为OpenWRT是小型软路由操作系统、网络协议栈知识内容十分丰富、望而生畏;我们先把庞大网络协议栈知识放一下,从易于入手的OpenWRT这个开源软路由开始入手,无论是网络协议栈还是OpenWRT的学习,总是要找到起点或入口;OpenWRT入口处就是 libubox ,因为其他软件是依托libubox库 api 接口构建起来的应用,请参考《详解 OpenWRT系统框架基础软件模块之libubox》
本篇文章分享的是libblobmsg_json库组件,这个库是 libubox的组成部分。
二、libblobmsg_json
OpenWrt支持c、shell、lua三种语言的进程通过ubus进行进程间通讯,ubus通讯的消息格式遵循json格式。
json(JavaScript object Notation)是一种轻量级的数据交换格式,易于人读写,也易于机器解析和生成。json是一种独立于编程语言之外的文本格式,兼容多种编程语言,如c、c++、Java、JavaScript、php、Python等。
json格式是{ key:value} 模式,数据类型如下:
-
string
格式是key:value,value为字符串;
object是一个name/vale对,格式是{name:value},相当于c语言中的结构体、哈希表等。 -
number
格式是key:value,value为整数; -
boolean
格式是key:value,value为1或者0; -
object
object相当于c语言中中的结构体,格式是key:{key1:value1,key2:value2,…},value可以是string、number、boolean、object或者array; -
array
array相当于c语言中的数组,格式是key:[value1,value2,…],value可以是string、number、boolean、object或者array。
2.1 json 脚本语言封装
OpenWrt 使用shell语言封装对json支持,在console中通过执行 source /usr/share/libubox/jshn.sh 脚本,初始env环境后、就可以调用脚本中封装的接口api,可参考 jshn.sh 分析shell具体封装方法,shell封装 json处理api 接口列表:
函数 | 描述 |
---|---|
json_init | 初始化json环境 |
json_cleanup | 清空json环境 |
json_add_string | 添加string类型的element |
json_add_int | 添加int类型的element |
json_add_boolean | 添加boolean类型的element |
json_add_table | |
json_close_table | 添加table类型的element |
json_add_array | |
json_close_array | 添加array类型的element |
json_load | 从字符串中导入到json格式 |
json_select | 进入到某个element,必须有是table或array才能使用json_select |
json_get_keys | 获取所有element的key |
json_get_values | 获取所有element的value |
json_get_var | 根据key获取value |
json_get_type | 获取element的类型 |
具体使用方法实例如下:
//产生 json 串用例
root@LEDE:~# source /usr/share/libubox/jshn.sh
root@LEDE:~# json_init
root@LEDE:~# json_add_string "str" "Hi, hela!"
root@LEDE:~# json_add_object "obj"
root@LEDE:~# json_add_int "num" "100"
root@LEDE:~# json_add_boolean "bool" "0"
root@LEDE:~# json_close_object
root@LEDE:~# json_add_array "array"
root@LEDE:~# json_add_string "arraystr" "array string"
root@LEDE:~# json_add_int "" "110"
root@LEDE:~# json_add_boolean "" "1"
root@LEDE:~# json_close_array
root@LEDE:~# MSG=`json_dump`
root@LEDE:~# echo ${MSG}
{ "str": "Hi, hela!", "obj": { "num": 100, "bool": false }, "array": [ "array string", 110, true ] }
// 解析 json 串用例
root@LEDE:~#
root@LEDE:~# json_load "$MSG"
root@LEDE:~# json_get_var varstr str
root@LEDE:~# json_select obj
root@LEDE:~# json_get_var varnum num
root@LEDE:~# json_get_var varbool bool
root@LEDE:~# json_select ..
root@LEDE:~# json_select array
root@LEDE:~# json_get_var array1 "1"
root@LEDE:~# json_get_var array2 "2"
root@LEDE:~# json_get_var array3 "3"
root@LEDE:~# cat << EOF
> {
> msg : $varstr,
> obj: {
> num : $varnum,
> bool : $varbool },
> array: [ $array1, $array2, $array3 ]
> }
> EOF
{
msg : Hi, hela!,
obj: {
num : 100,
bool : 0 },
array: [ array string, 110, 1 ]
}
通过环境变量保持参数:
// 执行 env 查看环境变量
root@LEDE:~# env
array2=110
T_J_A2_1=string
array3=1
T_J_A2_2=int
SSH_CLIENT=192.168.15.214 64657 22
USER=root
T_J_A2_3=boolean
T_J_V_obj=object
SHLVL=1
J_T1_num=100
HOME=/root
J_V_str=Hi, hela!
SSH_TTY=/dev/pts/0
T_J_T1_bool=boolean
J_V_array=J_A2
PS1=\[\e]0;\u@\h: \w\a\]\u@\h:\w\$
J_A2_1=array string
J_A2_2=110
J_A2_3=1
LOGNAME=root
K_J_A2= 1 2 3
J_V_obj=J_T1
varnum=100
TERM=xterm
J_T1_bool=0
PATH=/usr/sbin:/usr/bin:/sbin:/bin
DISPLAY=localhost:10.0
K_J_V= str obj array
varstr=Hi, hela!
SHELL=/bin/ash
varbool=0
T_J_T1_num=int
JSON_CUR=J_A2
PWD=/root
K_J_T1= num bool
T_J_V_str=string
SSH_CONNECTION=192.168.15.214 64657 192.168.15.1 22
T_J_V_array=array
array1=array string
//再次打印json 串内容
root@LEDE:~# json_dump
{ "str": "Hi, hela!", "obj": { "num": 100, "bool": false }, "array": [ "array string", 110, true ] }
//清楚 json 并查看 env 内容
root@LEDE:~# json_cleanup
root@LEDE:~# env
array2=110
array3=1
SSH_CLIENT=192.168.15.214 64657 22
USER=root
SHLVL=1
HOME=/root
SSH_TTY=/dev/pts/0
PS1=\[\e]0;\u@\h: \w\a\]\u@\h:\w\$
LOGNAME=root
varnum=100
TERM=xterm
PATH=/usr/sbin:/usr/bin:/sbin:/bin
DISPLAY=localhost:10.0
varstr=Hi, hela!
SHELL=/bin/ash
varbool=0
PWD=/root
SSH_CONNECTION=192.168.15.214 64657 192.168.15.1 22
array1=array string
//再次打印 json 内容
root@LEDE:~# json_dump
{ }
通过上面两个例子,应该对 shell 封装api接口有所了解。
2.2 C语言封装
OpenWrt中提供的C语言封装、数据格式采用 blob & blobmsg 形式组成的,blob是一种二进制字节序列,封装形式如下:
2.2.1 blob_attr 数据格式
struct blob_attr {
uint32_t id_len;
char data[];
};
数据包封装过程如下
#define BLOB_ATTR_EXTENDED 0x80000000 //blob-attr 扩展标志 1 bit
#define BLOB_ATTR_ID_MASK 0x7f000000 //blob-id: ID编码 7 bit
#define BLOB_ATTR_ID_SHIFT 24 //blob-len:数据长度
#define BLOB_ATTR_LEN_MASK 0x00ffffff
#define BLOB_ATTR_ALIGN 4
// 最后 id 和 数据长度函数
static void
blob_init(struct blob_attr *attr, int id, unsigned int len)
{
len &= BLOB_ATTR_LEN_MASK;
len |= (id << BLOB_ATTR_ID_SHIFT) & BLOB_ATTR_ID_MASK;
attr->id_len = cpu_to_be32(len);
}
//数据区内容
void
blob_fill_pad(struct blob_attr *attr)
{
char *buf = (char *) attr;
int len = blob_pad_len(attr);
int delta = len - blob_raw_len(attr);
if (delta > 0)
memset(buf + len - delta, 0, delta);
}
// 增加一个 blob_attr 元素接口
static struct blob_attr *
blob_add(struct blob_buf *buf, struct blob_attr *pos, int id, int payload)
{
int offset = attr_to_offset(buf, pos);
int required = (offset - BLOB_COOKIE + sizeof(struct blob_attr) + payload) - buf->buflen;
struct blob_attr *attr;
if (required > 0) {
if (!blob_buf_grow(buf, required))
return NULL;
attr = offset_to_attr(buf, offset);
} else {
attr = pos;
}
blob_init(attr, id, payload + sizeof(struct blob_attr));
blob_fill_pad(attr);
return attr;
}
由程序逻辑可描绘 blob-attr 内存中组成形式如下
- 1.*Data 是数据内容的指针、数据内容长度为 blob-len – (sizeof(struct blob_attr));
- 2.数据区内容,由专门接口负责申请内存、填充内容。
- 3.blob是符合TLV格式的,TLV表示Type-Length-Value,上图中,ID即使Type,Len即是Length,Payload即是Value。
2.2.2 blobmsg 数据格式
blobmsg是在blob的基础上扩展出来的,数据封装形式如下:
struct blob_buf {
struct blob_attr *head;
bool (*grow)(struct blob_buf *buf, int minlen); //封装的方法
int buflen; //增加数据区长度
void *buf; //数据区地址指针
};
struct blobmsg_policy {
const char *name;
enum blobmsg_type type;
};
enum blobmsg_type { //blobmsg 数据类型
BLOBMSG_TYPE_UNSPEC,
BLOBMSG_TYPE_ARRAY,
BLOBMSG_TYPE_TABLE,
BLOBMSG_TYPE_STRING,
BLOBMSG_TYPE_INT64,
BLOBMSG_TYPE_INT32,
BLOBMSG_TYPE_INT16,
BLOBMSG_TYPE_INT8,
BLOBMSG_TYPE_DOUBLE,
__BLOBMSG_TYPE_LAST,
BLOBMSG_TYPE_LAST = __BLOBMSG_TYPE_LAST - 1,
BLOBMSG_TYPE_BOOL = BLOBMSG_TYPE_INT8,
};
blobmsg数据格式封装过程如下:
struct blobmsg_hdr {
uint16_t namelen;
uint8_t name[];
} __packed;
// 增加 blobmsg 数据项的接口api , 以字符串类型为例
static inline int
blobmsg_add_string(struct blob_buf *buf, const char *name, const char *string)
{
return blobmsg_add_field(buf, BLOBMSG_TYPE_STRING, name, string, strlen(string) + 1);
}
// 函数 blobmsg_add_field 最终调用到 blobmsg_new 函数,此函数为数据格式封装。
static struct blob_attr *
blobmsg_new(struct blob_buf *buf, int type, const char *name, int payload_len, void **data)
{
struct blob_attr *attr;
struct blobmsg_hdr *hdr;
int attrlen, namelen;
char *pad_start, *pad_end;
if (!name)
name = "";
namelen = strlen(name);
attrlen = blobmsg_hdrlen(namelen) + payload_len;
attr = blob_new(buf, type, attrlen); //调用 blob_new 底层接口,BLOBMSG_TYPE_STRING 类型作为 blob_id 的内容。
if (!attr)
return NULL;
attr->id_len |= be32_to_cpu(BLOB_ATTR_EXTENDED); //处理blob_ext扩展位 和 数据长度
hdr = blob_data(attr);
hdr->namelen = cpu_to_be16(namelen);
memcpy(hdr->name, name, namelen);
hdr->name[namelen] = '\0';
pad_end = *data = blobmsg_data(attr); // 填充数据区内容
pad_start = (char *) &hdr->name[namelen];
if (pad_start < pad_end)
memset(pad_start, 0, pad_end - pad_start);
return attr;
}
blobmsg的string类型数据格式内存结构,如下图:
- 1.保留 blob_attr 基础结构不便
- 2.blob的数据区中,分为 blobmsg_hdr 、 key 数据 和 value 数据,
- 3.key 数据 和 value 数据的长度在 blobmsg_hdr 头中Namelen 描述
blobmsg中扩展标准位extended等于1,payload拆分出key和value两个字段,ID表示blogmsg的梳理类型,类型参考 enum blobmsg_type 定义。
由此我们基本理解 blobmsg 的数据封装形式,是在 blob_attr 基础上扩展出来,是对 blob_attr 的再次封装,主要提供接口内容如下:
string
blobmsg_add_string
blobmsg_get_string
int
blobmsg_add_u8
blobmsg_add_u16
blobmsg_add_u32
blobmsg_add_u64
blobmsg_get_u8
blobmsg_get_u16
blobmsg_get_u32
blobmsg_get_u64
bool
bool转换成u8的0或者1
table
blobmsg_open_table
blobmsg_close_table
2.2.3 blobmsg 用例
#include <stdio.h>
#include <float.h>
#include <limits.h>
#include <stdint.h>
#include <inttypes.h>
#include "blobmsg.h"
#include "blobmsg_json.h"
static const char *indent_str = "\t\t\t\t\t\t\t\t\t\t\t\t\t";
#define indent_printf(indent, ...) do { \
if (indent > 0) \
fwrite(indent_str, indent, 1, stderr); \
fprintf(stderr, __VA_ARGS__); \
} while(0)
static void dump_attr_data(struct blob_attr *data, int indent, int next_indent);
static void
dump_table(struct blob_attr *head, size_t len, int indent, bool array)
{
struct blob_attr *attr;
struct blobmsg_hdr *hdr;
indent_printf(indent, "{\n");
__blob_for_each_attr(attr, head, len) {
hdr = blob_data(attr);
if (!array)
indent_printf(indent + 1, "%s : ", hdr->name);
dump_attr_data(attr, 0, indent + 1);
}
indent_printf(indent, "}\n");
}
static void dump_attr_data(struct blob_attr *data, int indent, int next_indent)
{
int type = blobmsg_type(data);
switch(type) {
case BLOBMSG_TYPE_STRING:
indent_printf(indent, "%s (str)\n", blobmsg_get_string(data));
break;
case BLOBMSG_TYPE_INT8:
indent_printf(indent, "%d (i8)\n", (int8_t) blobmsg_get_u8(data));
break;
case BLOBMSG_TYPE_INT16:
indent_printf(indent, "%d (i16)\n", (int16_t) blobmsg_get_u16(data));
break;
case BLOBMSG_TYPE_INT32:
indent_printf(indent, "%d (i32)\n", (int32_t) blobmsg_get_u32(data));
break;
case BLOBMSG_TYPE_INT64:
indent_printf(indent, "%"PRId64" (i64)\n", (int64_t) blobmsg_get_u64(data));
break;
case BLOBMSG_TYPE_DOUBLE:
indent_printf(indent, "%lf (dbl)\n", blobmsg_get_double(data));
break;
case BLOBMSG_TYPE_TABLE:
case BLOBMSG_TYPE_ARRAY:
if (!indent)
indent_printf(indent, "\n");
dump_table(blobmsg_data(data), blobmsg_data_len(data),
next_indent, type == BLOBMSG_TYPE_ARRAY);
break;
}
}
enum {
FOO_MESSAGE,
FOO_LIST,
FOO_TESTDATA
};
static const struct blobmsg_policy pol[] = {
[FOO_MESSAGE] = {
.name = "message",
.type = BLOBMSG_TYPE_STRING,
},
[FOO_LIST] = {
.name = "list",
.type = BLOBMSG_TYPE_ARRAY,
},
[FOO_TESTDATA] = {
.name = "testdata",
.type = BLOBMSG_TYPE_TABLE,
},
};
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
static void dump_message(struct blob_buf *buf)
{
struct blob_attr *tb[ARRAY_SIZE(pol)];
if (blobmsg_parse(pol, ARRAY_SIZE(pol), tb, blob_data(buf->head), blob_len(buf->head)) != 0) {
fprintf(stderr, "Parse failed\n");
return;
}
if (tb[FOO_MESSAGE])
fprintf(stderr, "Message: %s\n", (char *) blobmsg_data(tb[FOO_MESSAGE]));
if (tb[FOO_LIST]) {
fprintf(stderr, "List: ");
dump_table(blobmsg_data(tb[FOO_LIST]), blobmsg_data_len(tb[FOO_LIST]), 0, true);
}
if (tb[FOO_TESTDATA]) {
fprintf(stderr, "Testdata: ");
dump_table(blobmsg_data(tb[FOO_TESTDATA]), blobmsg_data_len(tb[FOO_TESTDATA]), 0, false);
}
}
static void
fill_message(struct blob_buf *buf)
{
void *tbl;
blobmsg_add_string(buf, "message", "Hello, world!");
tbl = blobmsg_open_table(buf, "testdata");
blobmsg_add_double(buf, "dbl-min", DBL_MIN);
blobmsg_add_double(buf, "dbl-max", DBL_MAX);
blobmsg_add_u8(buf, "foo", 0);
blobmsg_add_u8(buf, "poo", 100);
blobmsg_add_u8(buf, "moo-min", INT8_MIN);
blobmsg_add_u8(buf, "moo-max", INT8_MAX);
blobmsg_add_u16(buf, "bar-min", INT16_MIN);
blobmsg_add_u16(buf, "bar-max", INT16_MAX);
blobmsg_add_u32(buf, "baz-min", INT32_MIN);
blobmsg_add_u32(buf, "baz-max", INT32_MAX);
blobmsg_add_u64(buf, "taz-min", INT64_MIN);
blobmsg_add_u64(buf, "taz-max", INT64_MAX);
blobmsg_add_string(buf, "world", "2");
blobmsg_close_table(buf, tbl);
tbl = blobmsg_open_array(buf, "list");
blobmsg_add_u8(buf, NULL, 0);
blobmsg_add_u8(buf, NULL, 100);
blobmsg_add_u8(buf, NULL, INT8_MIN);
blobmsg_add_u8(buf, NULL, INT8_MAX);
blobmsg_add_u16(buf, NULL, INT16_MIN);
blobmsg_add_u16(buf, NULL, INT16_MAX);
blobmsg_add_u32(buf, NULL, INT32_MIN);
blobmsg_add_u32(buf, NULL, INT32_MAX);
blobmsg_add_u64(buf, NULL, INT64_MIN);
blobmsg_add_u64(buf, NULL, INT64_MAX);
blobmsg_add_double(buf, NULL, DBL_MIN);
blobmsg_add_double(buf, NULL, DBL_MAX);
blobmsg_close_table(buf, tbl);
}
int main(int argc, char **argv)
{
char *json = NULL;
static struct blob_buf buf;
blobmsg_buf_init(&buf);
fill_message(&buf);
fprintf(stderr, "[*] blobmsg dump:\n");
dump_message(&buf);
json = blobmsg_format_json(buf.head, true);
if (!json)
exit(EXIT_FAILURE);
fprintf(stderr, "\n[*] blobmsg to json: %s\n", json);
blobmsg_buf_init(&buf);
if (!blobmsg_add_json_from_string(&buf, json))
exit(EXIT_FAILURE);
fprintf(stderr, "\n[*] blobmsg from json:\n");
dump_message(&buf);
if (buf.buf)
free(buf.buf);
free(json);
return 0;
}