31 –> 详解 OpenWRT系统框架基础软件模块之libblobmsg_json

  • Post author:
  • Post category:其他


一、简介

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;
}



版权声明:本文为weixin_38387929原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。