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

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 别欺骗自己了! 偶尔还是会想起你牛奶咖啡-时间的光 随着你遇到的事儿越来越多,做的事越来越多,你会不断完善与修正原...
  • 1.世界上最美的是心,最不会说谎的是心;最容易受伤的是心受伤后好不了的还是心。。。。。。是否:人类的痛与悲都要心来...
    阿顶爷阅读 309评论 0 0
  • 柳暗花明又一村 文/潘利伟 最近一段时间一直在为朗诵比赛忙碌着,从孩子们的朗诵内容,到孩子们朗诵时的一字一顿,表...
    我宝超萌萌萌哒阅读 404评论 0 7
  • 2019-03-29 姓名:李丽 六项精进第496期 利他二组 公司:南京泽朗生物科技有限公司 【日精进打卡第18...
    李丽南京泽朗阅读 145评论 0 1