物联网专用数据交换格式CBOR
前言
本文将介绍物联网领域的JSON格式——CBOR,CBOR是专门为受限制物联网终端设计的数据交换格式,该格式轻量间接,可以简单理解为二进制形式JSON格式。CBOR格式可以与COAP协议组合使用,犹如HTTP+JSON;另外,CBOR也是COSE的基础。
CBOR简述
CBOR可分为8个主类型(Major Type),CBOR格式为了定义8种不同的类型,采用首字节的高3位定义主类型。 首字节的低5位在不同的主类型表示长度(除主类型0和主类型1),如果长度指示不足,则依次使用后续字节。
主类型 | 名称 | 首字节 | 简单说明 |
---|---|---|---|
主类型0 | 无符号整数 | 0x00或 0x10 | 基础类型 |
主类型1 | 负整数 | 0x20或 0x30 | 基础类型 |
主类型2 | 字节数组 | 0x40或 0x50 | 基础类型 |
主类型3 | 字符串 | 0x60或 0x70 | 基础类型 |
主类型4 | 数组 | 0x80或 0x90 | 组合类型,可嵌套任意类型 |
主类型5 | 键值对 | 0xA0或 0xB0 | 组合类型,可嵌套任意类型 |
主类型6 | 扩展 | 0xC0或 0xD0 | 扩展类型 |
主类型7 | 数组 | 0xE0或 0xF0 | 浮点数与简单类型 |
无符号整数 an unsigned integer
主类型0,无符号整数编码后首字节为0b000_XXXXX。为了表达不同长度的无符号整数,CBOR格式使用第一个字节的低5位表示整数类型
- 0b000_11000 uint8_t
- 0b000_11001 uint16_t
- 0b000_11010 uint32_t
- 0b000_11011 uint64_t
请注意,无符号整数0到23直接表达,无需使用整数类型。
例如: - 10 编码后 0x0A
- 24 编码后 0x1818
- 100 编码后 0x1864
- 1000 编码后 0x1903E8
负整数 a negative integer
主类型1,无符号整数编码后首字节为0b001_XXXXX。负整数的编码方式与无符号整数相似。
例如:
- -10 编码后 0x29
- -24 编码后 0x37
- -100 编码后 0x3863
- -1000 编码后 0x3903E7
字节数组 a byte string
主类型2,字节数组编码后首字节为0b010_XXXXX。为了表达字节数组长度,如果字符数组的长度小于等于23,那么直接使用首字节的低5位表示;如果长度大于或等于24字节,那么使用第二个字节表示长度;如果长度大于等于256字节,那么使用第二和第三个字节表示长度。
CBOR长度说明.png
CBOR格式中一般采用多字节组合的方式表达长度。CBOR这样的长度描述方法便于嵌入式设备使用C语言解析CBOR格式,节约宝贵的栈空间与堆空间。
例如:
- HEX格式01020304 编码后 0x4401020304
- 长度为23的字节数组 编码后 0x57XX....
- 长度为24的字节数组 编码后 0x5818XX...
- 长度为100的字节数组 编码后 0x5901F4XX...
本质来说,CBOR仅为这些原始的字节数组增加了一个长度描述。
特别注意点
另外在CBOR格式编码钱的字节数组一般采用采用小写h开头,在单引号中描述HEX格式内容,例如
- h'01020304'
字符串 a text string
主类型3。字符串类型编码后首字节为0b011_XXXXX。字符串格式与字节数组格式非常相似,只是字节数组格式人类不可读,而字符格式人类可读。字符串格式表达长度的方式与字节数组类型相似。
例如:
- "a" 编码后 0x6161
- "IETF" 编码后 0x6449455446
- 长度为24的字符串 编码后 0x781830XX...
数组 an array of data items
主类型4。 数组编码后首字节为0b100_XXXXX。以上四种均为基础格式,而数组为一种符合,还可以与自身或其他类型嵌套。数组中数组元素个数(不是编码后字节长度)的表达方式与字节数组类型相似。
例如:
- [1,2,3] 编码后 0x83010203
- [1,[2,3], [4,5]] 编码后 0x8301820203820405,此处包括3个数组,第一个数组0x83,表示元素个数为3,第二个0x82b表示元素个数为2,第3个编码后元素个数为3。
对于数组部分,RFC7049也有些表述不清的地方。在主类型无符号整数中,若整数值超过24(0x18),该值将会被CBOR编码为0x1818,所以 - [24, 25, 26] 编码后为 0x8318181819181A,不是0x83181818。
- [500, 501, 502] 编码后为0x831901F41901F51901F6,不是0x8301F401F501F6
特别注意点
在JSON类型中,键名Key必须为字符串,但是在CBOR格式中,键名Key可以是整数。CBOR通过这种方式可以节省物联网终端开销。
键值对 a map of pairs of data items
主类型5。键值对编码后首字节为0b101_XXXXX。键值对也是一种符合类型,可以嵌套任意类型。键值对类型中键值对个数(不是编码后的字节长度)的表达方式与字节类型表达方式相似。例如
- {"a":1, "b":[2,3]} 编码后 0xA26161016162820203, 其中0x616101中 0x616101表示一个键值对,0x6161表示字符串编码"a", 0x01表示值1。其中0x6162820203表示另一个键值对,0x6162表示字符串编码"b",0x820203表示一个数组。
- {1:2, 3:4} 编码后 0xA201020304 (还需要分析,JSON中键名不能为数字,而CBOR可以)
扩展类型
主类型6。扩展类型编码后首字节为0b110_XXXXX。CBOR通过增加Tag的方式扩展类型,满足未来的扩展。COSE规范中通过CBOR Tag定义了多种新类型。本文暂不详细展开扩展类型,仅给出几个CBOR示例
- 23(h'01020304') 编码后 0xd74401020304
特别说明
在CBOR扩展类类型描述中,一般以Tag编号开头,然后在小括号中()保存内容,内容可以是任意一种CBOR类型。
浮点数与简单类型
主类型7。浮点数与简单类型编码后首字节为0b111_XXXXX。该类型定义了简单类型,时间类型(Date和Time)、大整数(Bignum),10进制整数(Decimal)等。在主类型7中,首字节的高3位固定为0b111,首字节中低5位用于表示子类型。
简单类型
首字节的低5位中0到23表示简单类,定义如下:
- 20 表达False
- 21 表达True
- 22 表达Null
- 23 表达Undefined Value
所以 - False 编码后 0xF4
- True 编码后 0xF5
- Null 编码后 0xF6
时间类型
CBOR体验
参考依赖
<!-- https://mvnrepository.com/artifact/com.upokecenter/cbor -->
<dependency>
<groupId>com.upokecenter</groupId>
<artifactId>cbor</artifactId>
<version>4.0.0-alpha2</version>
</dependency>
还依赖了两个参考库joda-time
和hexdump
<dependency>
<groupId>org.lasinger.tools</groupId>
<artifactId>hexdump</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.2</version>
</dependency>
整数相关
@Test
public void testInt() {
CBORObject obj = CBORObject.FromObject(1);
// 通过控制台打印
byte[] bytes = obj.EncodeToBytes();
String hexString = Hexdump.hexdump(bytes);
System.out.println(hexString);
}
@Test
public void testInt100() {
CBORObject obj = CBORObject.FromObject(100);
// 通过控制台打印,打印方法省略
}
@Test
public void testIntNegative100() {
CBORObject obj = CBORObject.FromObject(-100);
// 通过控制台打印,打印方法省略
}
字节数组与字符串
@Test
public void testByteArray() {
int length = 500;
byte[] testByte = new byte[length];
for (int i = 0; i < length; i++) {
testByte[i] = 0x30;
}
CBORObject obj = CBORObject.FromObject(testByte);
// 通过控制台打印,打印方法省略
}
@Test
public void testString() {
CBORObject obj = CBORObject.FromObject("IETF");
// 通过控制台打印,打印方法省略
}
@Test
public void testLargeString() {
int length = 24;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append("0");
}
CBORObject obj = CBORObject.FromObject(builder.toString());
// 通过控制台打印,打印方法省略
}
数组
@Test
public void testArray() {
CBORObject obj = CBORObject.NewArray();
obj.Add(CBORObject.FromObject(1));
obj.Add(CBORObject.FromObject(2));
obj.Add(CBORObject.FromObject(3));
// 通过控制台打印,打印方法省略
}
@Test
public void testArray24() {
CBORObject obj = CBORObject.NewArray();
obj.Add(CBORObject.FromObject(500));
obj.Add(CBORObject.FromObject(501));
obj.Add(CBORObject.FromObject(502));
// 通过控制台打印,打印方法省略
}
/**
* 嵌套数组 [1, [2,3], [4,5]]
*/
@Test
public void testMultiArray() {
CBORObject obj = CBORObject.NewArray();
obj.Add(CBORObject.FromObject(1));
CBORObject subArray1 = CBORObject.NewArray();
subArray1.Add(CBORObject.FromObject(2));
subArray1.Add(CBORObject.FromObject(3));
obj.Add(subArray1);
CBORObject subArray2 = CBORObject.NewArray();
subArray2.Add(CBORObject.FromObject(4));
subArray2.Add(CBORObject.FromObject(5));
obj.Add(subArray2);
// 通过控制台打印,打印方法省略
}
@Test
public void testLargeArray() {
CBORObject obj = CBORObject.NewArray();
int length = 25;
for (int i = 0; i < length; i++) {
int temp = i + 100;
obj.Add(CBORObject.FromObject(temp));
}
// 通过控制台打印,打印方法省略
}
键值对
@Test
public void testMap() {
CBORObject obj = CBORObject.NewMap();
obj.set(1, CBORObject.FromObject(2));
obj.set(3, CBORObject.FromObject(4));
// 通过控制台打印,打印方法省略
}
@Test
public void testJavaMap() {
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
CBORObject obj = CBORObject.FromObject(map);
// 通过控制台打印,打印方法省略
}
浮点型和简单类型
@Test
public void testTrue() {
CBORObject obj = CBORObject.FromObject(true);
byte[] bytes = obj.EncodeToBytes();
String hexString = Hexdump.hexdump(bytes);
System.out.println(hexString);
}
@Test
public void testBigDecimal() {
String decimalString = BigDecimal.valueOf(273.15).toString();
CBORObject obj = CBORObject.FromObject(EDecimal.FromString(decimalString));
// 通过控制台打印,打印方法省略
}
@Test
public void testDateTime() {
DateTime dt = new DateTime(2013, 3, 21, 20, 04, 0);
CBORObject obj = CBORObject.FromObject(dt.toDate());
// 通过控制台打印,打印方法省略
}
扩展类型
@Test
public void testCBORTag() {
byte[] array = new byte[] {0x01, 0x02, 0x03, 0x04};
CBORObject obj = CBORObject.FromObjectAndTag(array, 23);
System.out.println(obj.toString());
byte[] bytes = obj.EncodeToBytes();
String hexString = Hexdump.hexdump(bytes);
System.out.println(hexString);
}
总结
- CBOR格式是一种带有明显长度指示的传输协议,而常用的JSON格式并没有长度指示。长度指示可以帮助终端设备在进行CBOR解析时节约宝贵的堆空间。
- CBOR格式支持键值对形式 Key-Value,Key可以是整数,而JSON格式中Key值只能是字符串。
- CBOR格式中Date、Time、Decimal类型解决了物联网终端设备中时间日期与十进制数表达的问题。