| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:convert'; |
| import 'dart:typed_data'; |
| |
| import 'package:convert/convert.dart'; |
| import 'package:crypto/crypto.dart'; |
| |
| /** |
| * An instance of [ApiSignature] collects data in the form of primitive types |
| * (strings, ints, bools, etc.) from a summary "builder" object, and uses them |
| * to generate an MD5 signature of a the non-informative parts of the summary |
| * (i.e. those parts representing the API of the code being summarized). |
| * |
| * Note that the data passed to the MD5 signature algorithm is untyped. So, for |
| * instance, an API signature built from a sequence of `false` booleans is |
| * likely to match an API signature built from a sequence of zeros. The caller |
| * should take this into account; e.g. if a data structure may be represented |
| * either by a boolean or an int, the caller should encode a tag distinguishing |
| * the two representations before encoding the data. |
| */ |
| class ApiSignature { |
| /** |
| * Version number of the code in this class. Any time this class is changed |
| * in a way that affects the data collected in [_data], this version number |
| * should be incremented, so that a summary signed by a newer version of the |
| * signature algorithm won't accidentally have the same signature as a summary |
| * signed by an older version. |
| */ |
| static const int _VERSION = 0; |
| |
| /** |
| * Data accumulated so far. |
| */ |
| ByteData _data = new ByteData(4096); |
| |
| /** |
| * Offset into [_data] where the next byte should be written. |
| */ |
| int _offset = 0; |
| |
| /** |
| * Create an [ApiSignature] which is ready to accept data. |
| */ |
| ApiSignature() { |
| addInt(_VERSION); |
| } |
| |
| /** |
| * For testing only: create an [ApiSignature] which doesn't include any |
| * version information. This makes it easier to unit tests, since the data |
| * is stable even if [_VERSION] is changed. |
| */ |
| ApiSignature.unversioned(); |
| |
| /** |
| * Collect a boolean value. |
| */ |
| void addBool(bool b) { |
| _makeRoom(1); |
| _data.setUint8(_offset, b ? 1 : 0); |
| _offset++; |
| } |
| |
| /** |
| * Collect a sequence of arbitrary bytes. Note that the length is not |
| * collected, so for example `addBytes([1, 2]);` will have the same effect as |
| * `addBytes([1]); addBytes([2]);`. |
| */ |
| void addBytes(List<int> bytes) { |
| int length = bytes.length; |
| _makeRoom(length); |
| for (int i = 0; i < length; i++) { |
| _data.setUint8(_offset + i, bytes[i]); |
| } |
| _offset += length; |
| } |
| |
| /** |
| * Collect a double-precision floating point value. |
| */ |
| void addDouble(double d) { |
| _makeRoom(8); |
| _data.setFloat64(_offset, d, Endian.little); |
| _offset += 8; |
| } |
| |
| /** |
| * Collect a 32-bit unsigned integer value. |
| */ |
| void addInt(int i) { |
| _makeRoom(4); |
| _data.setUint32(_offset, i, Endian.little); |
| _offset += 4; |
| } |
| |
| /** |
| * Collect a string. |
| */ |
| void addString(String s) { |
| List<int> bytes = utf8.encode(s); |
| addInt(bytes.length); |
| addBytes(bytes); |
| } |
| |
| /** |
| * Collect the given [Uint32List]. |
| */ |
| void addUint32List(Uint32List data) { |
| addBytes(data.buffer.asUint8List()); |
| } |
| |
| /** |
| * For testing only: retrieve the internal representation of the data that |
| * has been collected. |
| */ |
| List<int> getBytes_forDebug() { |
| return new Uint8List.view(_data.buffer, 0, _offset).toList(); |
| } |
| |
| /** |
| * Return the bytes of the MD5 hash of the data collected so far. |
| */ |
| List<int> toByteList() { |
| return md5.convert(new Uint8List.view(_data.buffer, 0, _offset)).bytes; |
| } |
| |
| /** |
| * Return a hex-encoded MD5 signature of the data collected so far. |
| */ |
| String toHex() { |
| return hex.encode(toByteList()); |
| } |
| |
| /** |
| * Ensure that [spaceNeeded] bytes can be added to [_data] at [_offset] |
| * (copying it to a larger object if necessary). |
| */ |
| void _makeRoom(int spaceNeeded) { |
| int oldLength = _data.lengthInBytes; |
| if (_offset + spaceNeeded > oldLength) { |
| int newLength = 2 * (_offset + spaceNeeded); |
| ByteData newData = new ByteData(newLength); |
| new Uint8List.view(newData.buffer) |
| .setRange(0, oldLength, new Uint8List.view(_data.buffer)); |
| _data = newData; |
| } |
| } |
| } |