blob: 80937d68f704dbb5b969910e8d26ae710b3c7499 [file] [log] [blame]
// 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;
}
}
}