Line data Source code
1 : /*
2 : * Package : Cbor
3 : * Author : S. Hamblett <steve.hamblett@linux.com>
4 : * Date : 12/12/2016
5 : * Copyright : S.Hamblett
6 : */
7 :
8 : part of cbor;
9 :
10 : /// The encoder class implements the CBOR decoder functionality as defined in
11 : /// RFC7049.
12 : class Encoder {
13 : /// The output buffer
14 : Output _out;
15 :
16 : /// Indefinite sequence indicator, incremented on start
17 : /// decremented on stop.
18 : int _indefSequenceCount = 0;
19 :
20 3 : Encoder(Output out) {
21 3 : this._out = out;
22 : }
23 :
24 : /// Clears the output buffer.
25 : void clear() {
26 0 : _out.clear();
27 : }
28 :
29 : /// Booleans.
30 : void writeBool(bool value) {
31 : if (value) {
32 6 : _out.putByte(0xf5);
33 : } else {
34 6 : _out.putByte(0xf4);
35 : }
36 : }
37 :
38 : /// Positive and negative integers.
39 : void writeInt(int value) {
40 3 : if (value < 0) {
41 9 : _writeTypeValue(1, -(value + 1));
42 : } else {
43 3 : _writeTypeValue(0, value);
44 : }
45 : }
46 :
47 : /// Primitive byte writer.
48 : void writeBytes(typed.Uint8Buffer data) {
49 6 : _writeTypeValue(majorTypeBytes, data.length);
50 6 : _out.putBytes(data);
51 : }
52 :
53 : /// Raw byte buffer writer.
54 : /// No encoding is added to the buffer, it goes into the
55 : /// output stream as is.
56 : void writeRawBuffer(typed.Uint8Buffer buff) {
57 4 : _out.putBytes(buff);
58 : }
59 :
60 : /// Primitive string writer.
61 : void writeString(String str, [bool indefinite = false]) {
62 3 : final typed.Uint8Buffer buff = strToByteString(str);
63 : if (indefinite) {
64 2 : startIndefinite(majorTypeString);
65 : }
66 6 : _writeTypeValue(majorTypeString, buff.length);
67 6 : _out.putBytes(buff);
68 : }
69 :
70 : /// Bytestring primitive.
71 : void writeBuff(typed.Uint8Buffer data, [bool indefinite = false]) {
72 : if (indefinite) {
73 2 : startIndefinite(majorTypeBytes);
74 : }
75 4 : _writeTypeValue(majorTypeBytes, data.length);
76 4 : _out.putBytes(data);
77 : }
78 :
79 : /// Array primitive.
80 : /// Valid elements are string, integer, bool, float(any size), array
81 : /// or map. Returns true if the encoding has been successful.
82 : /// If you supply a length this will be used and not calculated from the
83 : /// array size, unless you are encoding certain indefinite sequences you
84 : /// do not need to do this.
85 : bool writeArray(List<dynamic> value,
86 : [bool indefinite = false, int length = null]) {
87 : // Mark the output buffer, if we cannot encode
88 : // the whole array structure rewind so as to perform
89 : // no encoding.
90 : bool res = true;
91 6 : _out.mark();
92 3 : final bool ok = writeArrayImpl(value, indefinite, length);
93 : if (!ok) {
94 0 : _out.resetToMark();
95 : res = false;
96 : }
97 : return res;
98 : }
99 :
100 : /// Map primitive.
101 : /// Valid map keys are integer and string. RFC7049
102 : /// recommends keys be of a single type, we are more generous
103 : /// here.
104 : /// Valid map values are integer, string, bool, float(any size), array
105 : /// map or buffer. Returns true if the encoding has been successful.
106 : bool writeMap(Map<dynamic, dynamic> value,
107 : [bool indefinite = false, int length = null]) {
108 : // Mark the output buffer, if we cannot encode
109 : // the whole map structure rewind so as to perform
110 : // no encoding.
111 : bool res = true;
112 6 : _out.mark();
113 3 : final bool ok = writeMapImpl(value, indefinite, length);
114 : if (!ok) {
115 0 : _out.resetToMark();
116 : res = false;
117 : }
118 : return res;
119 : }
120 :
121 : /// Tag primitive.
122 : void writeTag(int tag) {
123 3 : _writeTypeValue(majorTypeTag, tag);
124 : }
125 :
126 : /// Special(major type 7) primitive.
127 : void writeSpecial(int special) {
128 : int type = majorTypeSpecial;
129 3 : type <<= majorTypeShift;
130 9 : _out.putByte(type | special);
131 : }
132 :
133 : /// Null writer.
134 : void writeNull() {
135 6 : _out.putByte(0xf6);
136 : }
137 :
138 : /// Undefined writer.
139 : void writeUndefined() {
140 4 : _out.putByte(0xf7);
141 : }
142 :
143 : /// Indefinite item break primitive.
144 : void writeBreak() {
145 2 : writeSpecial(aiBreak);
146 4 : _indefSequenceCount--;
147 : }
148 :
149 : /// Indefinite item start.
150 : void startIndefinite(int majorType) {
151 8 : _out.putByte((majorType << 5) + aiBreak);
152 4 : _indefSequenceCount++;
153 : }
154 :
155 : /// Simple values, negative values, values over 255 or less
156 : /// than 0 will be encoded as an int.
157 : void writeSimple(int value) {
158 3 : if (!value.isNegative) {
159 6 : if ((value <= simpleLimitUpper) && (value >= simpleLimitLower)) {
160 3 : if (value <= ai23) {
161 3 : writeSpecial(value);
162 : } else {
163 1 : writeSpecial(ai24);
164 2 : _out.putByte(value);
165 : }
166 : } else {
167 1 : writeInt(value);
168 : }
169 : } else {
170 1 : writeInt(value);
171 : }
172 : }
173 :
174 : /// Generalised float encoder, picks the smallest encoding
175 : /// it can. If you want a specific precision use the more
176 : /// specialised methods.
177 : /// Note this can lead to encodings you may not expect in corner cases,
178 : /// if you want specific sized encodings don't use this.
179 : void writeFloat(double value) {
180 2 : if (canBeAHalf(value)) {
181 1 : writeHalf(value);
182 2 : } else if (canBeASingle(value)) {
183 1 : writeSingle(value);
184 : } else {
185 2 : writeDouble(value);
186 : }
187 : }
188 :
189 : /// Half precision float.
190 : void writeHalf(double value) {
191 3 : writeSpecial(ai25);
192 : // Special encodings
193 3 : if (value.isNaN) {
194 2 : _out.putByte(0x7e);
195 2 : _out.putByte(0x00);
196 : } else {
197 3 : final typed.Uint8Buffer valBuff = _singleToHalf(value);
198 9 : _out.putByte(valBuff[1]);
199 9 : _out.putByte(valBuff[0]);
200 : }
201 : }
202 :
203 : /// Single precision float.
204 : void writeSingle(double value) {
205 3 : writeSpecial(ai26);
206 : // Special encodings
207 3 : if (value.isNaN) {
208 2 : _out.putByte(0x7f);
209 2 : _out.putByte(0xc0);
210 2 : _out.putByte(0x00);
211 2 : _out.putByte(0x00);
212 : } else {
213 3 : final typed.Float32Buffer fBuff = new typed.Float32Buffer(1);
214 3 : fBuff[0] = value;
215 3 : final ByteBuffer bBuff = fBuff.buffer;
216 3 : final Uint8List uList = bBuff.asUint8List();
217 9 : _out.putByte(uList[3]);
218 9 : _out.putByte(uList[2]);
219 9 : _out.putByte(uList[1]);
220 9 : _out.putByte(uList[0]);
221 : }
222 : }
223 :
224 : /// Double precision float.
225 : void writeDouble(double value) {
226 3 : writeSpecial(ai27);
227 : // Special encodings
228 3 : if (value.isNaN) {
229 2 : _out.putByte(0x7f);
230 2 : _out.putByte(0xf8);
231 2 : _out.putByte(0x00);
232 2 : _out.putByte(0x00);
233 2 : _out.putByte(0x00);
234 2 : _out.putByte(0x00);
235 2 : _out.putByte(0x00);
236 2 : _out.putByte(0x00);
237 : } else {
238 3 : final typed.Float64Buffer fBuff = new typed.Float64Buffer(1);
239 3 : fBuff[0] = value;
240 3 : final ByteBuffer bBuff = fBuff.buffer;
241 3 : final Uint8List uList = bBuff.asUint8List();
242 9 : _out.putByte(uList[7]);
243 9 : _out.putByte(uList[6]);
244 9 : _out.putByte(uList[5]);
245 9 : _out.putByte(uList[4]);
246 9 : _out.putByte(uList[3]);
247 9 : _out.putByte(uList[2]);
248 9 : _out.putByte(uList[1]);
249 9 : _out.putByte(uList[0]);
250 : }
251 : }
252 :
253 : /// Tag based Date/Time encoding.
254 : /// Standard format as described in RFC339 et al.
255 : void writeDateTime(String dt) {
256 3 : writeTag(0);
257 3 : writeString(dt);
258 : }
259 :
260 : /// Tag based epoch encoding. Format can be a positive
261 : /// or negative integer or a floating point number for
262 : /// which you can chose the encoding.
263 : void writeEpoch(num epoch, [encodeFloatAs floatType = encodeFloatAs.single]) {
264 3 : writeTag(1);
265 6 : if (epoch.runtimeType == int) {
266 3 : writeInt(epoch);
267 : } else {
268 1 : if (floatType == encodeFloatAs.half) {
269 0 : writeHalf(epoch);
270 1 : } else if (floatType == encodeFloatAs.single) {
271 0 : writeSingle(epoch);
272 : } else {
273 1 : writeDouble(epoch);
274 : }
275 : }
276 : }
277 :
278 : /// Tag based Base64 byte string encoding. The encoder does not
279 : /// itself perform the base encoding as stated in RFC7049,
280 : /// it just indicates to the decoder that the following byte
281 : /// string maybe base encoded.
282 : void writeBase64(typed.Uint8Buffer data) {
283 3 : writeTag(22);
284 3 : writeBytes(data);
285 : }
286 :
287 : /// Cbor data item encoder, refer to tyhe RFC for details.
288 : void writeCborDi(typed.Uint8Buffer data) {
289 3 : writeTag(24);
290 3 : writeBytes(data);
291 : }
292 :
293 : /// Tag based Base64 URL byte string encoding. The encoder does not
294 : /// itself perform the base encoding as stated in RFC7049,
295 : /// it just indicates to the decoder that the following byte
296 : /// string maybe base encoded.
297 : void writeBase64URL(typed.Uint8Buffer data) {
298 2 : writeTag(21);
299 2 : writeBytes(data);
300 : }
301 :
302 : /// Tag based Base16 byte string encoding. The encoder does not
303 : /// itself perform the base encoding as stated in RFC7049,
304 : /// it just indicates to the decoder that the following byte
305 : /// string maybe base encoded.
306 : void writeBase16(typed.Uint8Buffer data) {
307 3 : writeTag(23);
308 3 : writeBytes(data);
309 : }
310 :
311 : /// Tag based URI writer
312 : void writeURI(String uri) {
313 3 : writeTag(32);
314 3 : writeString(uri);
315 : }
316 :
317 : /// Helper functions
318 :
319 : /// Lookup table based single to half precision conversion.
320 : /// Rounding is indeterminate.
321 : typed.Uint8Buffer _singleToHalf(double value) {
322 3 : final int hBits = getHalfPrecisionInt(value);
323 3 : final typed.Uint16Buffer hBuff = new typed.Uint16Buffer(1);
324 3 : hBuff[0] = hBits;
325 3 : final ByteBuffer lBuff = hBuff.buffer;
326 3 : final Uint8List hList = lBuff.asUint8List();
327 3 : final typed.Uint8Buffer valBuff = new typed.Uint8Buffer();
328 3 : valBuff.addAll(hList);
329 : return valBuff;
330 : }
331 :
332 : /// Encoding helper for type encoding.
333 : void _writeTypeValue(int majorType, int value) {
334 : int type = majorType;
335 3 : type <<= majorTypeShift;
336 3 : if (value < ai24) {
337 : // Value
338 9 : _out.putByte((type | value));
339 6 : } else if (value < two8) {
340 : // Uint8
341 9 : _out.putByte((type | ai24));
342 6 : _out.putByte(value);
343 6 : } else if (value < two16) {
344 : // Uint16
345 9 : _out.putByte((type | ai25));
346 3 : final typed.Uint16Buffer buff = new typed.Uint16Buffer(1);
347 3 : buff[0] = value;
348 6 : final Uint8List ulist = new Uint8List.view(buff.buffer);
349 3 : final typed.Uint8Buffer data = new typed.Uint8Buffer();
350 3 : data.addAll(ulist
351 3 : .toList()
352 3 : .reversed);
353 6 : _out.putBytes(data);
354 6 : } else if (value < two32) {
355 : // Uint32
356 9 : _out.putByte((type | ai26));
357 3 : final typed.Uint32Buffer buff = new typed.Uint32Buffer(1);
358 3 : buff[0] = value;
359 6 : final Uint8List ulist = new Uint8List.view(buff.buffer);
360 3 : final typed.Uint8Buffer data = new typed.Uint8Buffer();
361 3 : data.addAll(ulist
362 3 : .toList()
363 3 : .reversed);
364 6 : _out.putBytes(data);
365 2 : } else if (value < two64) {
366 : // Uint64
367 3 : _out.putByte((type | ai27));
368 1 : final typed.Uint64Buffer buff = new typed.Uint64Buffer(1);
369 1 : buff[0] = value;
370 2 : final Uint8List ulist = new Uint8List.view(buff.buffer);
371 1 : final typed.Uint8Buffer data = new typed.Uint8Buffer();
372 1 : data.addAll(ulist
373 1 : .toList()
374 1 : .reversed);
375 2 : _out.putBytes(data);
376 : } else {
377 : // Bignum - not supported, use tags
378 0 : print("Bignums not supported");
379 : }
380 : }
381 :
382 : /// String to byte string helper.
383 : typed.Uint8Buffer strToByteString(String str) {
384 3 : final typed.Uint8Buffer buff = new typed.Uint8Buffer();
385 3 : final convertor.Utf8Encoder utf = new convertor.Utf8Encoder();
386 3 : final List<int> codes = utf.convert(str);
387 3 : buff.addAll(codes);
388 : return buff;
389 : }
390 :
391 : /// Array write implementation method.
392 : /// If the array cannot be fully encoded no encoding occurs,
393 : /// ie false is returned.
394 : bool writeArrayImpl(List<dynamic> value,
395 : [bool indefinite = false, int length = null]) {
396 : // Check for empty
397 3 : if (value.isEmpty) {
398 : if (!indefinite) {
399 2 : _writeTypeValue(majorTypeArray, 0);
400 : } else {
401 1 : startIndefinite(majorTypeArray);
402 : }
403 : return true;
404 : }
405 :
406 : // Build the encoded array.
407 : if (!indefinite) {
408 : if (length != null) {
409 1 : _writeTypeValue(majorTypeArray, length);
410 : } else {
411 6 : _writeTypeValue(majorTypeArray, value.length);
412 : }
413 : } else {
414 2 : startIndefinite(majorTypeArray);
415 : }
416 :
417 : bool ok = true;
418 6 : for (dynamic element in value) {
419 6 : String valType = element.runtimeType.toString();
420 3 : if (valType.contains("List")) {
421 : valType = "List";
422 : }
423 3 : if (valType.contains("Map")) {
424 : valType = "Map";
425 : }
426 : switch (valType) {
427 3 : case "int":
428 3 : writeInt(element);
429 : break;
430 1 : case "String":
431 1 : writeString(element);
432 : break;
433 1 : case "double":
434 0 : writeFloat(element);
435 : break;
436 1 : case "List":
437 : if (!indefinite) {
438 1 : final bool res = writeArrayImpl(element, indefinite);
439 : if (!res) {
440 : // Fail the whole encoding
441 : ok = false;
442 : }
443 : } else {
444 0 : for (int a in element) {
445 0 : _out.putByte(a);
446 : }
447 : }
448 : break;
449 1 : case "Map":
450 : if (!indefinite) {
451 1 : final bool res = writeMapImpl(element, indefinite);
452 : if (!res) {
453 : // Fail the whole encoding
454 : ok = false;
455 : }
456 : } else {
457 0 : for (int a in element) {
458 0 : _out.putByte(a);
459 : }
460 : }
461 : break;
462 0 : case "bool":
463 0 : writeBool(element);
464 : break;
465 0 : case "Null":
466 0 : writeNull();
467 : break;
468 0 : case "Uint8Buffer":
469 0 : writeRawBuffer(element);
470 : break;
471 : default:
472 0 : print("writeArrayImpl::RT is ${element.runtimeType.toString()}");
473 : ok = false;
474 : }
475 : }
476 : return ok;
477 : }
478 :
479 : /// Map write implementation method.
480 : /// If the map cannot be fully encoded no encoding occurs,
481 : /// ie false is returned.
482 : bool writeMapImpl(Map<dynamic, dynamic> value,
483 : [bool indefinite = false, int length = null]) {
484 : // Check for empty
485 3 : if (value.isEmpty) {
486 : if (!indefinite) {
487 1 : _writeTypeValue(majorTypeMap, 0);
488 : }
489 : return true;
490 : }
491 :
492 : // Check the keys are integers or strings.
493 3 : final dynamic keys = value.keys;
494 : bool keysValid = true;
495 6 : for (dynamic element in keys) {
496 9 : if (!(element.runtimeType.toString() == "int") &&
497 9 : !(element.runtimeType.toString() == "String")) {
498 : keysValid = false;
499 : break;
500 : }
501 : }
502 : if (!keysValid) {
503 : return false;
504 : }
505 : // Build the encoded map.
506 : if (!indefinite) {
507 6 : if (_indefSequenceCount == 0) {
508 : if (length != null) {
509 0 : _writeTypeValue(majorTypeMap, length);
510 : } else {
511 6 : _writeTypeValue(majorTypeMap, value.length);
512 : }
513 : }
514 : } else {
515 2 : startIndefinite(majorTypeMap);
516 : }
517 :
518 : bool ok = true;
519 3 : value.forEach((dynamic key, dynamic val) {
520 : // Encode the key, can now only be ints or strings.
521 9 : if (key.runtimeType.toString() == "int") {
522 2 : writeInt(key);
523 : } else {
524 3 : writeString(key);
525 : }
526 : // Encode the value
527 6 : String valType = val.runtimeType.toString();
528 3 : if (valType.contains("List")) {
529 : valType = "List";
530 : }
531 3 : if (valType.contains("Map")) {
532 : valType = "Map";
533 : }
534 : switch (valType) {
535 3 : case "int":
536 2 : writeInt(val);
537 : break;
538 3 : case "String":
539 3 : writeString(val);
540 : break;
541 3 : case "double":
542 0 : writeFloat(val);
543 : break;
544 3 : case "List":
545 : if (!indefinite) {
546 3 : final bool res = writeArrayImpl(val, indefinite);
547 : if (!res) {
548 : // Fail the whole encoding
549 : ok = false;
550 : }
551 : } else {
552 2 : for (int a in val) {
553 2 : _out.putByte(a);
554 : }
555 : }
556 : break;
557 3 : case "Map":
558 : if (!indefinite) {
559 1 : final bool res = writeMapImpl(val, indefinite);
560 : if (!res) {
561 : // Fail the whole encoding
562 : ok = false;
563 : }
564 : } else {
565 0 : for (int a in val) {
566 0 : _out.putByte(a);
567 : }
568 : }
569 : break;
570 3 : case "bool":
571 3 : writeBool(val);
572 : break;
573 1 : case "Null":
574 1 : writeNull();
575 : break;
576 1 : case "Uint8Buffer":
577 1 : writeRawBuffer(val);
578 : break;
579 : default:
580 0 : print("writeMapImpl::RT is ${val.runtimeType.toString()}");
581 : ok = false;
582 : }
583 : });
584 : return ok;
585 : }
586 : }
|