物聯網專用數據交換格式 CBOR
前言
本文將介紹物聯網領域的 JSON 格式——CBOR,CBOR 是專門爲受限制物聯網終端設計的數據交換格式,該格式輕量間接,可以簡單理解爲二進制形式 JSON 格式。CBOR 格式可以與 COAP 協議組合使用,猶如 HTTP+JSON;另外,CBOR 也是 COSE 的基礎。
CBOR 簡述
CBOR 可分爲 8 個主類型(Major Type),CBOR 格式爲了定義 8 種不同的類型,採用首字節的高 3 位定義主類型。 首字節的低 5 位在不同的主類型表示長度(除主類型 0 和主類型 1),如果長度指示不足,則依次使用後續字節。
無符號整數 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 這樣的長度描述方法便於嵌入式設備使用 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 類型解決了物聯網終端設備中時間日期與十進制數表達的問題。
參考資料
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://www.jianshu.com/p/76adec5e61f8