UNPKG

10.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.encode = encode;
4exports.decode = decode;
5exports.decodeWithLists = decodeWithLists;
6exports.decodeList = decodeList;
7exports.readUInt64LE = readUInt64LE;
8exports.writeUInt32 = writeUInt32;
9exports.readUInt32 = readUInt32;
10exports.writeFloat32LE = writeFloat32LE;
11exports.writeUInt16 = writeUInt16;
12exports.readUInt16 = readUInt16;
13exports.readVariableUIntLE = readVariableUIntLE;
14exports.writeVariableUIntLE = writeVariableUIntLE;
15const tslib_1 = require("tslib");
16const assert_1 = tslib_1.__importDefault(require("assert"));
17const hapCrypto = tslib_1.__importStar(require("../util/hapCrypto"));
18/**
19 * Type Length Value encoding/decoding, used by HAP as a wire format.
20 * https://en.wikipedia.org/wiki/Type-length-value
21 */
22const EMPTY_TLV_TYPE = 0x00; // and empty tlv with id 0 is usually used as delimiter for tlv lists
23/**
24 * @group TLV8
25 */
26// eslint-disable-next-line @typescript-eslint/no-explicit-any
27function encode(type, data, ...args) {
28 const encodedTLVBuffers = [];
29 // coerce data to Buffer if needed
30 if (typeof data === "number") {
31 data = Buffer.from([data]);
32 }
33 else if (typeof data === "string") {
34 data = Buffer.from(data);
35 }
36 if (Array.isArray(data)) {
37 let first = true;
38 for (const entry of data) {
39 if (!first) {
40 encodedTLVBuffers.push(Buffer.from([EMPTY_TLV_TYPE, 0])); // push delimiter
41 }
42 first = false;
43 encodedTLVBuffers.push(encode(type, entry));
44 }
45 if (first) { // we have a zero length array!
46 encodedTLVBuffers.push(Buffer.from([type, 0]));
47 }
48 }
49 else if (data.length <= 255) {
50 encodedTLVBuffers.push(Buffer.concat([Buffer.from([type, data.length]), data]));
51 }
52 else { // otherwise it doesn't fit into one tlv entry, thus we push multiple
53 let leftBytes = data.length;
54 let currentIndex = 0;
55 for (; leftBytes > 0;) {
56 if (leftBytes >= 255) {
57 encodedTLVBuffers.push(Buffer.concat([Buffer.from([type, 0xFF]), data.slice(currentIndex, currentIndex + 255)]));
58 leftBytes -= 255;
59 currentIndex += 255;
60 }
61 else {
62 encodedTLVBuffers.push(Buffer.concat([Buffer.from([type, leftBytes]), data.slice(currentIndex)]));
63 leftBytes -= leftBytes;
64 }
65 }
66 }
67 // do we have more arguments to encode?
68 if (args.length >= 2) {
69 // chop off the first two arguments which we already processed, and process the rest recursively
70 const [nextType, nextData, ...nextArgs] = args;
71 const remainingTLVBuffer = encode(nextType, nextData, ...nextArgs);
72 // append the remaining encoded arguments directly to the buffer
73 encodedTLVBuffers.push(remainingTLVBuffer);
74 }
75 return Buffer.concat(encodedTLVBuffers);
76}
77/**
78 * This method is the legacy way of decoding tlv data.
79 * It will not properly decode multiple list of the same id.
80 * Should the decoder encounter multiple instances of the same id, it will just concatenate the buffer data.
81 *
82 * @param buffer - TLV8 data
83 *
84 * Note: Please use {@link decodeWithLists} which properly decodes list elements.
85 *
86 * @group TLV8
87 */
88function decode(buffer) {
89 (0, assert_1.default)(buffer instanceof Buffer, "Illegal argument. tlv.decode() expects Buffer type!");
90 const objects = {};
91 let leftLength = buffer.length;
92 let currentIndex = 0;
93 for (; leftLength > 0;) {
94 const type = buffer[currentIndex];
95 const length = buffer[currentIndex + 1];
96 currentIndex += 2;
97 leftLength -= 2;
98 const data = buffer.slice(currentIndex, currentIndex + length);
99 if (objects[type]) {
100 objects[type] = Buffer.concat([objects[type], data]);
101 }
102 else {
103 objects[type] = data;
104 }
105 currentIndex += length;
106 leftLength -= length;
107 }
108 return objects;
109}
110/**
111 * Decode a buffer coding TLV8 encoded entries.
112 *
113 * This method decodes multiple entries split by a TLV delimiter properly into Buffer arrays.
114 * It properly reassembles tlv entries if they were split across multiple entries due to exceeding the max tlv entry size of 255 bytes.
115 * @param buffer - The Buffer containing TLV8 encoded data.
116 *
117 * @group TLV8
118 */
119function decodeWithLists(buffer) {
120 const result = {};
121 let leftBytes = buffer.length;
122 let readIndex = 0;
123 let lastType = -1;
124 let lastLength = -1;
125 let lastItemWasDelimiter = false;
126 for (; leftBytes > 0;) {
127 const type = buffer.readUInt8(readIndex++);
128 const length = buffer.readUInt8(readIndex++);
129 leftBytes -= 2;
130 const data = buffer.slice(readIndex, readIndex + length);
131 readIndex += length;
132 leftBytes -= length;
133 if (type === 0 && length === 0) {
134 lastItemWasDelimiter = true;
135 continue;
136 }
137 const existing = result[type];
138 if (existing) { // there is already an item with the same type
139 if (lastItemWasDelimiter && lastType === type) { // list of tlv types
140 if (Array.isArray(existing)) {
141 existing.push(data);
142 }
143 else {
144 result[type] = [existing, data];
145 }
146 }
147 else if (lastType === type && lastLength === 255) { // tlv data got split into multiple entries as length exceeded 255
148 if (Array.isArray(existing)) {
149 // append to the last data blob in the array
150 const last = existing[existing.length - 1];
151 existing[existing.length - 1] = Buffer.concat([last, data]);
152 }
153 else {
154 result[type] = Buffer.concat([existing, data]);
155 }
156 }
157 else {
158 throw new Error(`Found duplicated tlv entry with type ${type} and length ${length} `
159 + `(lastItemWasDelimiter: ${lastItemWasDelimiter}, lastType: ${lastType}, lastLength: ${lastLength})`);
160 }
161 }
162 else {
163 result[type] = data;
164 }
165 lastType = type;
166 lastLength = length;
167 lastItemWasDelimiter = false;
168 }
169 return result;
170}
171/**
172 * This method can be used to parse a TLV8 encoded list that was concatenated.
173 *
174 * If you are thinking about using this method, try to refactor the code to use {@link decodeWithLists} instead of {@link decode}.
175 * The single reason of this method's existence are the shortcomings {@link decode}, as it concatenates multiple tlv8 list entries
176 * into a single Buffer.
177 * This method can be used to undo that, by specifying the concatenated buffer and the tlv id of the element that should
178 * mark the beginning of a new tlv8 list entry.
179 *
180 * @param data - The concatenated tlv8 list entries (probably output of {@link decode}).
181 * @param entryStartId - The tlv id that marks the beginning of a new tlv8 entry.
182 *
183 * @group TLV8
184 */
185function decodeList(data, entryStartId) {
186 const objectsList = [];
187 let leftLength = data.length;
188 let currentIndex = 0;
189 let objects = undefined;
190 for (; leftLength > 0;) {
191 const type = data[currentIndex]; // T
192 const length = data[currentIndex + 1]; // L
193 const value = data.slice(currentIndex + 2, currentIndex + 2 + length); // V
194 if (type === entryStartId) { // we got the start of a new entry
195 if (objects !== undefined) { // save the previous entry
196 objectsList.push(objects);
197 }
198 objects = {};
199 }
200 if (objects === undefined) {
201 throw new Error("Error parsing tlv list: Encountered uninitialized storage object");
202 }
203 if (objects[type]) { // append to buffer if we have already data for this type
204 objects[type] = Buffer.concat([objects[type], value]);
205 }
206 else {
207 objects[type] = value;
208 }
209 currentIndex += 2 + length;
210 leftLength -= 2 + length;
211 }
212 if (objects !== undefined) {
213 objectsList.push(objects);
214 } // push last entry
215 return objectsList;
216}
217/**
218 * @group TLV8
219 */
220function readUInt64LE(buffer, offset = 0) {
221 const low = buffer.readUInt32LE(offset);
222 // javascript doesn't allow to shift by 32(?), therefore we multiply here
223 return buffer.readUInt32LE(offset + 4) * 0x100000000 + low;
224}
225/**
226 * `writeUint32LE`
227 * @group TLV8
228 */
229function writeUInt32(value) {
230 const buffer = Buffer.alloc(4);
231 buffer.writeUInt32LE(value, 0);
232 return buffer;
233}
234/**
235 * `readUInt32LE`
236 * @group TLV8
237 */
238function readUInt32(buffer) {
239 return buffer.readUInt32LE(0);
240}
241/**
242 * @group TLV8
243 */
244function writeFloat32LE(value) {
245 const buffer = Buffer.alloc(4);
246 buffer.writeFloatLE(value, 0);
247 return buffer;
248}
249/**
250 * `writeUInt16LE`
251 * @group TLV8
252 */
253function writeUInt16(value) {
254 const buffer = Buffer.alloc(2);
255 buffer.writeUInt16LE(value, 0);
256 return buffer;
257}
258/**
259 * `readUInt16LE`
260 * @group TLV8
261 */
262function readUInt16(buffer) {
263 return buffer.readUInt16LE(0);
264}
265/**
266 * Reads variable size unsigned integer {@link writeVariableUIntLE}.
267 * @param buffer - The buffer to read from. It must have exactly the size of the given integer.
268 * @group TLV8
269 */
270function readVariableUIntLE(buffer) {
271 switch (buffer.length) {
272 case 1:
273 return buffer.readUInt8(0);
274 case 2:
275 return buffer.readUInt16LE(0);
276 case 4:
277 return buffer.readUInt32LE(0);
278 case 8:
279 return readUInt64LE(buffer, 0);
280 default:
281 throw new Error("Can't read uint LE with length " + buffer.length);
282 }
283}
284/**
285 * Writes variable size unsigned integer.
286 * Either:
287 * - `UInt8`
288 * - `UInt16LE`
289 * - `UInt32LE`
290 * @param number
291 * @group TLV8
292 */
293function writeVariableUIntLE(number) {
294 (0, assert_1.default)(number >= 0, "Can't encode a negative integer as unsigned integer");
295 if (number <= 255) {
296 const buffer = Buffer.alloc(1);
297 buffer.writeUInt8(number, 0);
298 return buffer;
299 }
300 else if (number <= 65535) {
301 return writeUInt16(number);
302 }
303 else if (number <= 4294967295) {
304 return writeUInt32(number);
305 }
306 else {
307 const buffer = Buffer.alloc(8);
308 hapCrypto.writeUInt64LE(number, buffer, 0);
309 return buffer;
310 }
311}
312//# sourceMappingURL=tlv.js.map
\No newline at end of file