物联网专用数据交换格式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长度描述
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-timehexdump

<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类型解决了物联网终端设备中时间日期与十进制数表达的问题。

参考资料


版权声明:本文为xukai871105原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/xukai871105/article/details/96607961