LCOV - code coverage report
Current view: top level - src - cbor_encoder.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 203 226 89.8 %
Date: 2017-04-06 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.10