From 8e6809c29a9b4e0c1e8db93ce6325fc54b284aa5 Mon Sep 17 00:00:00 2001 From: JohnE Date: Thu, 18 Jul 2019 01:28:58 -0700 Subject: [PATCH] NEW: dart-uuid -> convert -> crypto --- packages/convert/.gitignore | 9 + packages/convert/.test_config | 3 + packages/convert/.travis.yml | 23 ++ packages/convert/AUTHORS | 6 + packages/convert/CHANGELOG.md | 42 ++ packages/convert/CONTRIBUTING.md | 33 ++ packages/convert/LICENSE | 26 ++ packages/convert/README.md | 6 + packages/convert/analysis_options.yaml | 43 +++ packages/convert/lib/convert.dart | 12 + .../convert/lib/src/accumulator_sink.dart | 37 ++ .../lib/src/byte_accumulator_sink.dart | 53 +++ packages/convert/lib/src/hex.dart | 29 ++ packages/convert/lib/src/hex/decoder.dart | 172 +++++++++ packages/convert/lib/src/hex/encoder.dart | 89 +++++ packages/convert/lib/src/identity_codec.dart | 28 ++ packages/convert/lib/src/percent.dart | 38 ++ packages/convert/lib/src/percent/decoder.dart | 244 ++++++++++++ packages/convert/lib/src/percent/encoder.dart | 106 +++++ .../lib/src/string_accumulator_sink.dart | 46 +++ packages/convert/lib/src/utils.dart | 39 ++ packages/convert/pubspec.yaml | 16 + packages/crypto/.gitignore | 3 + packages/crypto/.test_config | 5 + packages/crypto/.travis.yml | 24 ++ packages/crypto/AUTHORS | 6 + packages/crypto/CHANGELOG.md | 103 +++++ packages/crypto/CONTRIBUTING.md | 33 ++ packages/crypto/LICENSE | 26 ++ packages/crypto/README.md | 113 ++++++ packages/crypto/analysis_options.yaml | 43 +++ packages/crypto/lib/crypto.dart | 10 + packages/crypto/lib/src/digest.dart | 29 ++ packages/crypto/lib/src/digest_sink.dart | 30 ++ packages/crypto/lib/src/hash.dart | 35 ++ packages/crypto/lib/src/hash_sink.dart | 158 ++++++++ packages/crypto/lib/src/hmac.dart | 107 +++++ packages/crypto/lib/src/md5.dart | 124 ++++++ packages/crypto/lib/src/sha1.dart | 104 +++++ packages/crypto/lib/src/sha256.dart | 141 +++++++ packages/crypto/lib/src/utils.dart | 22 ++ packages/crypto/pubspec.yaml | 17 + packages/dart-uuid/.gitignore | 11 + packages/dart-uuid/.travis.yml | 6 + packages/dart-uuid/CHANGELOG.md | 164 ++++++++ packages/dart-uuid/LICENSE | 7 + packages/dart-uuid/README.md | 277 +++++++++++++ packages/dart-uuid/analysis_options.yaml | 19 + packages/dart-uuid/lib/uuid.dart | 364 ++++++++++++++++++ packages/dart-uuid/lib/uuid_util.dart | 30 ++ packages/dart-uuid/lib/uuidv4.dart | 37 ++ packages/dart-uuid/pubspec.yaml | 14 + packages/dart-uuid/test.html | 6 + 53 files changed, 3168 insertions(+) create mode 100644 packages/convert/.gitignore create mode 100644 packages/convert/.test_config create mode 100644 packages/convert/.travis.yml create mode 100644 packages/convert/AUTHORS create mode 100644 packages/convert/CHANGELOG.md create mode 100644 packages/convert/CONTRIBUTING.md create mode 100644 packages/convert/LICENSE create mode 100644 packages/convert/README.md create mode 100644 packages/convert/analysis_options.yaml create mode 100644 packages/convert/lib/convert.dart create mode 100644 packages/convert/lib/src/accumulator_sink.dart create mode 100644 packages/convert/lib/src/byte_accumulator_sink.dart create mode 100644 packages/convert/lib/src/hex.dart create mode 100644 packages/convert/lib/src/hex/decoder.dart create mode 100644 packages/convert/lib/src/hex/encoder.dart create mode 100644 packages/convert/lib/src/identity_codec.dart create mode 100644 packages/convert/lib/src/percent.dart create mode 100644 packages/convert/lib/src/percent/decoder.dart create mode 100644 packages/convert/lib/src/percent/encoder.dart create mode 100644 packages/convert/lib/src/string_accumulator_sink.dart create mode 100644 packages/convert/lib/src/utils.dart create mode 100644 packages/convert/pubspec.yaml create mode 100644 packages/crypto/.gitignore create mode 100644 packages/crypto/.test_config create mode 100644 packages/crypto/.travis.yml create mode 100644 packages/crypto/AUTHORS create mode 100644 packages/crypto/CHANGELOG.md create mode 100644 packages/crypto/CONTRIBUTING.md create mode 100644 packages/crypto/LICENSE create mode 100644 packages/crypto/README.md create mode 100644 packages/crypto/analysis_options.yaml create mode 100644 packages/crypto/lib/crypto.dart create mode 100644 packages/crypto/lib/src/digest.dart create mode 100644 packages/crypto/lib/src/digest_sink.dart create mode 100644 packages/crypto/lib/src/hash.dart create mode 100644 packages/crypto/lib/src/hash_sink.dart create mode 100644 packages/crypto/lib/src/hmac.dart create mode 100644 packages/crypto/lib/src/md5.dart create mode 100644 packages/crypto/lib/src/sha1.dart create mode 100644 packages/crypto/lib/src/sha256.dart create mode 100644 packages/crypto/lib/src/utils.dart create mode 100644 packages/crypto/pubspec.yaml create mode 100644 packages/dart-uuid/.gitignore create mode 100644 packages/dart-uuid/.travis.yml create mode 100644 packages/dart-uuid/CHANGELOG.md create mode 100644 packages/dart-uuid/LICENSE create mode 100644 packages/dart-uuid/README.md create mode 100644 packages/dart-uuid/analysis_options.yaml create mode 100644 packages/dart-uuid/lib/uuid.dart create mode 100644 packages/dart-uuid/lib/uuid_util.dart create mode 100644 packages/dart-uuid/lib/uuidv4.dart create mode 100644 packages/dart-uuid/pubspec.yaml create mode 100644 packages/dart-uuid/test.html diff --git a/packages/convert/.gitignore b/packages/convert/.gitignore new file mode 100644 index 0000000..25a1df3 --- /dev/null +++ b/packages/convert/.gitignore @@ -0,0 +1,9 @@ +.buildlog +.DS_Store +.idea +.pub/ +.settings/ +build/ +packages +.packages +pubspec.lock diff --git a/packages/convert/.test_config b/packages/convert/.test_config new file mode 100644 index 0000000..352d2fe --- /dev/null +++ b/packages/convert/.test_config @@ -0,0 +1,3 @@ +{ + "test_package": true +} diff --git a/packages/convert/.travis.yml b/packages/convert/.travis.yml new file mode 100644 index 0000000..e8ffe6d --- /dev/null +++ b/packages/convert/.travis.yml @@ -0,0 +1,23 @@ +language: dart + +dart: + - dev + - 2.0.0 + +dart_task: + - test: --platform vm + - test: --platform chrome + - dartanalyzer: --fatal-warnings --fatal-infos . + +matrix: + include: + - dart: dev + dart_task: dartfmt + +# Only building master means that we don't run two builds for each pull request. +branches: + only: [master] + +cache: + directories: + - $HOME/.pub-cache diff --git a/packages/convert/AUTHORS b/packages/convert/AUTHORS new file mode 100644 index 0000000..e8063a8 --- /dev/null +++ b/packages/convert/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. diff --git a/packages/convert/CHANGELOG.md b/packages/convert/CHANGELOG.md new file mode 100644 index 0000000..d3b3410 --- /dev/null +++ b/packages/convert/CHANGELOG.md @@ -0,0 +1,42 @@ +## 2.1.1 + + * Fixed a DDC compilation regression for consumers using the Dart 1.x SDK that was introduced in `2.1.0`. + +## 2.1.0 + + * Added an `IdentityCodec` which implements `Codec` for use as default + value for in functions accepting an optional `Codec` as parameter. + +## 2.0.2 + +* Set max SDK version to `<3.0.0`, and adjust other dependencies. + +## 2.0.1 + +* `PercentEncoder` no longer encodes digits. This follows the specified + behavior. + +## 2.0.0 + +**Note**: No new APIs have been added in 2.0.0. Packages that would use 2.0.0 as +a lower bound should use 1.0.0 instead—for example, `convert: ">=1.0.0 <3.0.0"`. + +* `HexDecoder`, `HexEncoder`, `PercentDecoder`, and `PercentEncoder` no longer + extend `ChunkedConverter`. + +## 1.1.1 + +* Fix all strong-mode warnings. + +## 1.1.0 + +* Add `AccumulatorSink`, `ByteAccumulatorSink`, and `StringAccumulatorSink` + classes for providing synchronous access to the output of chunked converters. + +## 1.0.1 + +* Small improvement in percent decoder efficiency. + +## 1.0.0 + +* Initial version diff --git a/packages/convert/CONTRIBUTING.md b/packages/convert/CONTRIBUTING.md new file mode 100644 index 0000000..6f5e0ea --- /dev/null +++ b/packages/convert/CONTRIBUTING.md @@ -0,0 +1,33 @@ +Want to contribute? Great! First, read this page (including the small print at +the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. + +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. + +### File headers +All files in the project must start with the following header. + + // Copyright (c) 2015, 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. + +### The small print +Contributions made by corporations are covered by a different agreement than the +one above, the +[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). diff --git a/packages/convert/LICENSE b/packages/convert/LICENSE new file mode 100644 index 0000000..de31e1a --- /dev/null +++ b/packages/convert/LICENSE @@ -0,0 +1,26 @@ +Copyright 2015, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/convert/README.md b/packages/convert/README.md new file mode 100644 index 0000000..9a832fd --- /dev/null +++ b/packages/convert/README.md @@ -0,0 +1,6 @@ +# Conversion utilities package + +This package contains encoders and decoders for converting between different +data representations. It's the external counterpart of the `dart:convert` core +library, and contains less-central APIs and APIs that need more flexible +versioning. diff --git a/packages/convert/analysis_options.yaml b/packages/convert/analysis_options.yaml new file mode 100644 index 0000000..0711aca --- /dev/null +++ b/packages/convert/analysis_options.yaml @@ -0,0 +1,43 @@ +include: package:pedantic/analysis_options.yaml +analyzer: + strong-mode: + implicit-casts: false +linter: + rules: + - avoid_empty_else + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_types + - cancel_subscriptions + - constant_identifier_names + - control_flow_in_finally + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + - hash_and_equals + - implementation_imports + - iterable_contains_unrelated_type + - library_names + - library_prefixes + - list_remove_unrelated_type + - non_constant_identifier_names + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_generic_function_type_aliases + - prefer_is_not_empty + - slash_for_doc_comments + - test_types_in_equals + - throw_in_finally + - type_init_formals + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_new + - unrelated_type_equality_checks + - valid_regexps diff --git a/packages/convert/lib/convert.dart b/packages/convert/lib/convert.dart new file mode 100644 index 0000000..d745856 --- /dev/null +++ b/packages/convert/lib/convert.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2015, 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. + +library convert; + +export 'src/accumulator_sink.dart'; +export 'src/byte_accumulator_sink.dart'; +export 'src/hex.dart'; +export 'src/identity_codec.dart'; +export 'src/percent.dart'; +export 'src/string_accumulator_sink.dart'; diff --git a/packages/convert/lib/src/accumulator_sink.dart b/packages/convert/lib/src/accumulator_sink.dart new file mode 100644 index 0000000..d92b4a8 --- /dev/null +++ b/packages/convert/lib/src/accumulator_sink.dart @@ -0,0 +1,37 @@ +// 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:collection'; + +/// A sink that provides access to all the [events] that have been passed to it. +/// +/// See also [ChunkedConversionSink.withCallback]. +class AccumulatorSink implements Sink { + /// An unmodifiable list of events passed to this sink so far. + List get events => UnmodifiableListView(_events); + final _events = []; + + /// Whether [close] has been called. + bool get isClosed => _isClosed; + var _isClosed = false; + + /// Removes all events from [events]. + /// + /// This can be used to avoid double-processing events. + void clear() { + _events.clear(); + } + + void add(T event) { + if (_isClosed) { + throw StateError("Can't add to a closed sink."); + } + + _events.add(event); + } + + void close() { + _isClosed = true; + } +} diff --git a/packages/convert/lib/src/byte_accumulator_sink.dart b/packages/convert/lib/src/byte_accumulator_sink.dart new file mode 100644 index 0000000..d1fab06 --- /dev/null +++ b/packages/convert/lib/src/byte_accumulator_sink.dart @@ -0,0 +1,53 @@ +// 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:typed_data/typed_data.dart'; + +/// A sink that provides access to the concatenated bytes passed to it. +/// +/// See also [ByteConversionSink.withCallback]. +class ByteAccumulatorSink extends ByteConversionSinkBase { + /// The bytes accumulated so far. + /// + /// The returned [Uint8List] is viewing a shared buffer, so it should not be + /// changed and any bytes outside the view should not be accessed. + Uint8List get bytes => Uint8List.view(_buffer.buffer, 0, _buffer.length); + + final _buffer = Uint8Buffer(); + + /// Whether [close] has been called. + bool get isClosed => _isClosed; + var _isClosed = false; + + /// Removes all bytes from [bytes]. + /// + /// This can be used to avoid double-processing data. + void clear() { + _buffer.clear(); + } + + void add(List bytes) { + if (_isClosed) { + throw StateError("Can't add to a closed sink."); + } + + _buffer.addAll(bytes); + } + + void addSlice(List chunk, int start, int end, bool isLast) { + if (_isClosed) { + throw StateError("Can't add to a closed sink."); + } + + _buffer.addAll(chunk, start, end); + if (isLast) _isClosed = true; + } + + void close() { + _isClosed = true; + } +} diff --git a/packages/convert/lib/src/hex.dart b/packages/convert/lib/src/hex.dart new file mode 100644 index 0000000..28e9203 --- /dev/null +++ b/packages/convert/lib/src/hex.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2015, 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. + +library convert.hex; + +import 'dart:convert'; + +import 'hex/decoder.dart'; +import 'hex/encoder.dart'; + +export 'hex/decoder.dart' hide hexDecoder; +export 'hex/encoder.dart' hide hexEncoder; + +/// The canonical instance of [HexCodec]. +const hex = HexCodec._(); + +/// A codec that converts byte arrays to and from hexadecimal strings, following +/// [the Base16 spec][rfc]. +/// +/// [rfc]: https://tools.ietf.org/html/rfc4648#section-8 +/// +/// This should be used via the [hex] field. +class HexCodec extends Codec, String> { + HexEncoder get encoder => hexEncoder; + HexDecoder get decoder => hexDecoder; + + const HexCodec._(); +} diff --git a/packages/convert/lib/src/hex/decoder.dart b/packages/convert/lib/src/hex/decoder.dart new file mode 100644 index 0000000..e37d5c9 --- /dev/null +++ b/packages/convert/lib/src/hex/decoder.dart @@ -0,0 +1,172 @@ +// Copyright (c) 2015, 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. + +library convert.hex.decoder; + +import 'dart:convert'; +import 'dart:typed_data'; + +import '../utils.dart'; + +/// The canonical instance of [HexDecoder]. +const hexDecoder = HexDecoder._(); + +/// A converter that decodes hexadecimal strings into byte arrays. +/// +/// Because two hexadecimal digits correspond to a single byte, this will throw +/// a [FormatException] if given an odd-length string. It will also throw a +/// [FormatException] if given a string containing non-hexadecimal code units. +class HexDecoder extends Converter> { + const HexDecoder._(); + + List convert(String string) { + if (!string.length.isEven) { + throw FormatException( + "Invalid input length, must be even.", string, string.length); + } + + var bytes = Uint8List(string.length ~/ 2); + _decode(string.codeUnits, 0, string.length, bytes, 0); + return bytes; + } + + StringConversionSink startChunkedConversion(Sink> sink) => + _HexDecoderSink(sink); +} + +/// A conversion sink for chunked hexadecimal decoding. +class _HexDecoderSink extends StringConversionSinkBase { + /// The underlying sink to which decoded byte arrays will be passed. + final Sink> _sink; + + /// The trailing digit from the previous string. + /// + /// This will be non-`null` if the most recent string had an odd number of + /// hexadecimal digits. Since it's the most significant digit, it's always a + /// multiple of 16. + int _lastDigit; + + _HexDecoderSink(this._sink); + + void addSlice(String string, int start, int end, bool isLast) { + RangeError.checkValidRange(start, end, string.length); + + if (start == end) { + if (isLast) _close(string, end); + return; + } + + var codeUnits = string.codeUnits; + Uint8List bytes; + int bytesStart; + if (_lastDigit == null) { + bytes = Uint8List((end - start) ~/ 2); + bytesStart = 0; + } else { + var hexPairs = (end - start - 1) ~/ 2; + bytes = Uint8List(1 + hexPairs); + bytes[0] = _lastDigit + digitForCodeUnit(codeUnits, start); + start++; + bytesStart = 1; + } + + _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart); + + _sink.add(bytes); + if (isLast) _close(string, end); + } + + ByteConversionSink asUtf8Sink(bool allowMalformed) => + _HexDecoderByteSink(_sink); + + void close() => _close(); + + /// Like [close], but includes [string] and [index] in the [FormatException] + /// if one is thrown. + void _close([String string, int index]) { + if (_lastDigit != null) { + throw FormatException( + "Input ended with incomplete encoded byte.", string, index); + } + + _sink.close(); + } +} + +/// A conversion sink for chunked hexadecimal decoding from UTF-8 bytes. +class _HexDecoderByteSink extends ByteConversionSinkBase { + /// The underlying sink to which decoded byte arrays will be passed. + final Sink> _sink; + + /// The trailing digit from the previous string. + /// + /// This will be non-`null` if the most recent string had an odd number of + /// hexadecimal digits. Since it's the most significant digit, it's always a + /// multiple of 16. + int _lastDigit; + + _HexDecoderByteSink(this._sink); + + void add(List chunk) => addSlice(chunk, 0, chunk.length, false); + + void addSlice(List chunk, int start, int end, bool isLast) { + RangeError.checkValidRange(start, end, chunk.length); + + if (start == end) { + if (isLast) _close(chunk, end); + return; + } + + Uint8List bytes; + int bytesStart; + if (_lastDigit == null) { + bytes = Uint8List((end - start) ~/ 2); + bytesStart = 0; + } else { + var hexPairs = (end - start - 1) ~/ 2; + bytes = Uint8List(1 + hexPairs); + bytes[0] = _lastDigit + digitForCodeUnit(chunk, start); + start++; + bytesStart = 1; + } + + _lastDigit = _decode(chunk, start, end, bytes, bytesStart); + + _sink.add(bytes); + if (isLast) _close(chunk, end); + } + + void close() => _close(); + + /// Like [close], but includes [chunk] and [index] in the [FormatException] + /// if one is thrown. + void _close([List chunk, int index]) { + if (_lastDigit != null) { + throw FormatException( + "Input ended with incomplete encoded byte.", chunk, index); + } + + _sink.close(); + } +} + +/// Decodes [codeUnits] and writes the result into [destination]. +/// +/// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes +/// the result into [destination] starting at [destinationStart]. +/// +/// If there's a leftover digit at the end of the decoding, this returns that +/// digit. Otherwise it returns `null`. +int _decode(List codeUnits, int sourceStart, int sourceEnd, + List destination, int destinationStart) { + var destinationIndex = destinationStart; + for (var i = sourceStart; i < sourceEnd - 1; i += 2) { + var firstDigit = digitForCodeUnit(codeUnits, i); + var secondDigit = digitForCodeUnit(codeUnits, i + 1); + destination[destinationIndex++] = 16 * firstDigit + secondDigit; + } + + if ((sourceEnd - sourceStart).isEven) return null; + return 16 * digitForCodeUnit(codeUnits, sourceEnd - 1); +} diff --git a/packages/convert/lib/src/hex/encoder.dart b/packages/convert/lib/src/hex/encoder.dart new file mode 100644 index 0000000..3ed85c7 --- /dev/null +++ b/packages/convert/lib/src/hex/encoder.dart @@ -0,0 +1,89 @@ +// Copyright (c) 2015, 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. + +library convert.hex.encoder; + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:charcode/ascii.dart'; + +/// The canonical instance of [HexEncoder]. +const hexEncoder = HexEncoder._(); + +/// A converter that encodes byte arrays into hexadecimal strings. +/// +/// This will throw a [RangeError] if the byte array has any digits that don't +/// fit in the gamut of a byte. +class HexEncoder extends Converter, String> { + const HexEncoder._(); + + String convert(List bytes) => _convert(bytes, 0, bytes.length); + + ByteConversionSink startChunkedConversion(Sink sink) => + _HexEncoderSink(sink); +} + +/// A conversion sink for chunked hexadecimal encoding. +class _HexEncoderSink extends ByteConversionSinkBase { + /// The underlying sink to which decoded byte arrays will be passed. + final Sink _sink; + + _HexEncoderSink(this._sink); + + void add(List chunk) { + _sink.add(_convert(chunk, 0, chunk.length)); + } + + void addSlice(List chunk, int start, int end, bool isLast) { + RangeError.checkValidRange(start, end, chunk.length); + _sink.add(_convert(chunk, start, end)); + if (isLast) _sink.close(); + } + + void close() { + _sink.close(); + } +} + +String _convert(List bytes, int start, int end) { + // A Uint8List is more efficient than a StringBuffer given that we know that + // we're only emitting ASCII-compatible characters, and that we know the + // length ahead of time. + var buffer = Uint8List((end - start) * 2); + var bufferIndex = 0; + + // A bitwise OR of all bytes in [bytes]. This allows us to check for + // out-of-range bytes without adding more branches than necessary to the + // core loop. + var byteOr = 0; + for (var i = start; i < end; i++) { + var byte = bytes[i]; + byteOr |= byte; + + // The bitwise arithmetic here is equivalent to `byte ~/ 16` and `byte % 16` + // for valid byte values, but is easier for dart2js to optimize given that + // it can't prove that [byte] will always be positive. + buffer[bufferIndex++] = _codeUnitForDigit((byte & 0xF0) >> 4); + buffer[bufferIndex++] = _codeUnitForDigit(byte & 0x0F); + } + + if (byteOr >= 0 && byteOr <= 255) return String.fromCharCodes(buffer); + + // If there was an invalid byte, find it and throw an exception. + for (var i = start; i < end; i++) { + var byte = bytes[i]; + if (byte >= 0 && byte <= 0xff) continue; + throw FormatException( + "Invalid byte ${byte < 0 ? "-" : ""}0x${byte.abs().toRadixString(16)}.", + bytes, + i); + } + + throw 'unreachable'; +} + +/// Returns the ASCII/Unicode code unit corresponding to the hexadecimal digit +/// [digit]. +int _codeUnitForDigit(int digit) => digit < 10 ? digit + $0 : digit + $a - 10; diff --git a/packages/convert/lib/src/identity_codec.dart b/packages/convert/lib/src/identity_codec.dart new file mode 100644 index 0000000..9da7bca --- /dev/null +++ b/packages/convert/lib/src/identity_codec.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +class _IdentityConverter extends Converter { + _IdentityConverter(); + T convert(T input) => input; +} + +/// A [Codec] that performs the identity conversion (changing nothing) in both +/// directions. +/// +/// The identity codec passes input directly to output in both directions. +/// This class can be used as a base when combining multiple codecs, +/// because fusing the identity codec with any other codec gives the other +/// codec back. +/// +/// Note, that when fused with another [Codec] the identity codec disppears. +class IdentityCodec extends Codec { + const IdentityCodec(); + + Converter get decoder => _IdentityConverter(); + Converter get encoder => _IdentityConverter(); + + /// Fuse with an other codec. + /// + /// Fusing with the identify converter is a no-op, so this always return + /// [other]. + Codec fuse(Codec other) => other; +} diff --git a/packages/convert/lib/src/percent.dart b/packages/convert/lib/src/percent.dart new file mode 100644 index 0000000..b06d08f --- /dev/null +++ b/packages/convert/lib/src/percent.dart @@ -0,0 +1,38 @@ +// Copyright (c) 2015, 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. + +library convert.percent; + +import 'dart:convert'; + +import 'percent/decoder.dart'; +import 'percent/encoder.dart'; + +export 'percent/decoder.dart' hide percentDecoder; +export 'percent/encoder.dart' hide percentEncoder; + +/// The canonical instance of [PercentCodec]. +const percent = PercentCodec._(); + +// TODO(nweiz): Add flags to support generating and interpreting "+" as a space +// character. Also add an option for custom sets of unreserved characters. +/// A codec that converts byte arrays to and from percent-encoded (also known as +/// URL-encoded) strings according to [RFC 3986][rfc]. +/// +/// [rfc]: https://tools.ietf.org/html/rfc3986#section-2.1 +/// +/// [encoder] encodes all bytes other than ASCII letters, decimal digits, or one +/// of `-._~`. This matches the behavior of [Uri.encodeQueryComponent] except +/// that it doesn't encode `0x20` bytes to the `+` character. +/// +/// To be maximally flexible, [decoder] will decode any percent-encoded byte and +/// will allow any non-percent-encoded byte other than `%`. By default, it +/// interprets `+` as `0x2B` rather than `0x20` as emitted by +/// [Uri.encodeQueryComponent]. +class PercentCodec extends Codec, String> { + PercentEncoder get encoder => percentEncoder; + PercentDecoder get decoder => percentDecoder; + + const PercentCodec._(); +} diff --git a/packages/convert/lib/src/percent/decoder.dart b/packages/convert/lib/src/percent/decoder.dart new file mode 100644 index 0000000..cc403ff --- /dev/null +++ b/packages/convert/lib/src/percent/decoder.dart @@ -0,0 +1,244 @@ +// Copyright (c) 2015, 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. + +library convert.percent.decoder; + +import 'dart:convert'; + +import 'package:charcode/ascii.dart'; +import 'package:typed_data/typed_data.dart'; + +import '../utils.dart'; + +/// The canonical instance of [PercentDecoder]. +const percentDecoder = PercentDecoder._(); + +const _lastPercent = -1; + +/// A converter that decodes percent-encoded strings into byte arrays. +/// +/// To be maximally flexible, this will decode any percent-encoded byte and +/// will allow any non-percent-encoded byte other than `%`. By default, it +/// interprets `+` as `0x2B` rather than `0x20` as emitted by +/// [Uri.encodeQueryComponent]. +/// +/// This will throw a [FormatException] if the input string has an incomplete +/// percent-encoding, or if it contains non-ASCII code units. +class PercentDecoder extends Converter> { + const PercentDecoder._(); + + List convert(String string) { + var buffer = Uint8Buffer(); + var lastDigit = _decode(string.codeUnits, 0, string.length, buffer); + + if (lastDigit != null) { + throw FormatException( + "Input ended with incomplete encoded byte.", string, string.length); + } + + return buffer.buffer.asUint8List(0, buffer.length); + } + + StringConversionSink startChunkedConversion(Sink> sink) => + _PercentDecoderSink(sink); +} + +/// A conversion sink for chunked percent-encoded decoding. +class _PercentDecoderSink extends StringConversionSinkBase { + /// The underlying sink to which decoded byte arrays will be passed. + final Sink> _sink; + + /// The trailing digit from the previous string. + /// + /// This is `null` if the previous string ended with a complete + /// percent-encoded byte or a literal character. It's [_lastPercent] if the + /// most recent string ended with `%`. Otherwise, the most recent string ended + /// with a `%` followed by a hexadecimal digit, and this is that digit. Since + /// it's the most significant digit, it's always a multiple of 16. + int _lastDigit; + + _PercentDecoderSink(this._sink); + + void addSlice(String string, int start, int end, bool isLast) { + RangeError.checkValidRange(start, end, string.length); + + if (start == end) { + if (isLast) _close(string, end); + return; + } + + var buffer = Uint8Buffer(); + var codeUnits = string.codeUnits; + if (_lastDigit == _lastPercent) { + _lastDigit = 16 * digitForCodeUnit(codeUnits, start); + start++; + + if (start == end) { + if (isLast) _close(string, end); + return; + } + } + + if (_lastDigit != null) { + buffer.add(_lastDigit + digitForCodeUnit(codeUnits, start)); + start++; + } + + _lastDigit = _decode(codeUnits, start, end, buffer); + + _sink.add(buffer.buffer.asUint8List(0, buffer.length)); + if (isLast) _close(string, end); + } + + ByteConversionSink asUtf8Sink(bool allowMalformed) => + _PercentDecoderByteSink(_sink); + + void close() => _close(); + + /// Like [close], but includes [string] and [index] in the [FormatException] + /// if one is thrown. + void _close([String string, int index]) { + if (_lastDigit != null) { + throw FormatException( + "Input ended with incomplete encoded byte.", string, index); + } + + _sink.close(); + } +} + +/// A conversion sink for chunked percent-encoded decoding from UTF-8 bytes. +class _PercentDecoderByteSink extends ByteConversionSinkBase { + /// The underlying sink to which decoded byte arrays will be passed. + final Sink> _sink; + + /// The trailing digit from the previous string. + /// + /// This is `null` if the previous string ended with a complete + /// percent-encoded byte or a literal character. It's [_lastPercent] if the + /// most recent string ended with `%`. Otherwise, the most recent string ended + /// with a `%` followed by a hexadecimal digit, and this is that digit. Since + /// it's the most significant digit, it's always a multiple of 16. + int _lastDigit; + + _PercentDecoderByteSink(this._sink); + + void add(List chunk) => addSlice(chunk, 0, chunk.length, false); + + void addSlice(List chunk, int start, int end, bool isLast) { + RangeError.checkValidRange(start, end, chunk.length); + + if (start == end) { + if (isLast) _close(chunk, end); + return; + } + + var buffer = Uint8Buffer(); + if (_lastDigit == _lastPercent) { + _lastDigit = 16 * digitForCodeUnit(chunk, start); + start++; + + if (start == end) { + if (isLast) _close(chunk, end); + return; + } + } + + if (_lastDigit != null) { + buffer.add(_lastDigit + digitForCodeUnit(chunk, start)); + start++; + } + + _lastDigit = _decode(chunk, start, end, buffer); + + _sink.add(buffer.buffer.asUint8List(0, buffer.length)); + if (isLast) _close(chunk, end); + } + + void close() => _close(); + + /// Like [close], but includes [chunk] and [index] in the [FormatException] + /// if one is thrown. + void _close([List chunk, int index]) { + if (_lastDigit != null) { + throw FormatException( + "Input ended with incomplete encoded byte.", chunk, index); + } + + _sink.close(); + } +} + +/// Decodes [codeUnits] and writes the result into [destination]. +/// +/// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes +/// the result into [destination] starting at [destinationStart]. +/// +/// If there's a leftover digit at the end of the decoding, this returns that +/// digit. Otherwise it returns `null`. +int _decode(List codeUnits, int start, int end, Uint8Buffer buffer) { + // A bitwise OR of all code units in [codeUnits]. This allows us to check for + // out-of-range code units without adding more branches than necessary to the + // core loop. + var codeUnitOr = 0; + + // The beginning of the current slice of adjacent non-% characters. We can add + // all of these to the buffer at once. + var sliceStart = start; + for (var i = start; i < end; i++) { + // First, loop through non-% characters. + var codeUnit = codeUnits[i]; + if (codeUnits[i] != $percent) { + codeUnitOr |= codeUnit; + continue; + } + + // We found a %. The slice from `sliceStart` to `i` represents characters + // than can be copied to the buffer as-is. + if (i > sliceStart) { + _checkForInvalidCodeUnit(codeUnitOr, codeUnits, sliceStart, i); + buffer.addAll(codeUnits, sliceStart, i); + } + + // Now decode the percent-encoded byte and add it as well. + i++; + if (i >= end) return _lastPercent; + + var firstDigit = digitForCodeUnit(codeUnits, i); + i++; + if (i >= end) return 16 * firstDigit; + + var secondDigit = digitForCodeUnit(codeUnits, i); + buffer.add(16 * firstDigit + secondDigit); + + // The next iteration will look for non-% characters again. + sliceStart = i + 1; + } + + if (end > sliceStart) { + _checkForInvalidCodeUnit(codeUnitOr, codeUnits, sliceStart, end); + if (start == sliceStart) { + buffer.addAll(codeUnits); + } else { + buffer.addAll(codeUnits, sliceStart, end); + } + } + + return null; +} + +void _checkForInvalidCodeUnit( + int codeUnitOr, List codeUnits, int start, int end) { + if (codeUnitOr >= 0 && codeUnitOr <= 0x7f) return; + + for (var i = start; i < end; i++) { + var codeUnit = codeUnits[i]; + if (codeUnit >= 0 && codeUnit <= 0x7f) continue; + throw FormatException( + "Non-ASCII code unit " + "U+${codeUnit.toRadixString(16).padLeft(4, '0')}", + codeUnits, + i); + } +} diff --git a/packages/convert/lib/src/percent/encoder.dart b/packages/convert/lib/src/percent/encoder.dart new file mode 100644 index 0000000..390316f --- /dev/null +++ b/packages/convert/lib/src/percent/encoder.dart @@ -0,0 +1,106 @@ +// Copyright (c) 2015, 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. + +library convert.percent.encoder; + +import 'dart:convert'; + +import 'package:charcode/ascii.dart'; + +/// The canonical instance of [PercentEncoder]. +const percentEncoder = PercentEncoder._(); + +/// A converter that encodes byte arrays into percent-encoded strings. +/// +/// [encoder] encodes all bytes other than ASCII letters, decimal digits, or one +/// of `-._~`. This matches the behavior of [Uri.encodeQueryComponent] except +/// that it doesn't encode `0x20` bytes to the `+` character. +/// +/// This will throw a [RangeError] if the byte array has any digits that don't +/// fit in the gamut of a byte. +class PercentEncoder extends Converter, String> { + const PercentEncoder._(); + + String convert(List bytes) => _convert(bytes, 0, bytes.length); + + ByteConversionSink startChunkedConversion(Sink sink) => + _PercentEncoderSink(sink); +} + +/// A conversion sink for chunked percentadecimal encoding. +class _PercentEncoderSink extends ByteConversionSinkBase { + /// The underlying sink to which decoded byte arrays will be passed. + final Sink _sink; + + _PercentEncoderSink(this._sink); + + void add(List chunk) { + _sink.add(_convert(chunk, 0, chunk.length)); + } + + void addSlice(List chunk, int start, int end, bool isLast) { + RangeError.checkValidRange(start, end, chunk.length); + _sink.add(_convert(chunk, start, end)); + if (isLast) _sink.close(); + } + + void close() { + _sink.close(); + } +} + +String _convert(List bytes, int start, int end) { + var buffer = StringBuffer(); + + // A bitwise OR of all bytes in [bytes]. This allows us to check for + // out-of-range bytes without adding more branches than necessary to the + // core loop. + var byteOr = 0; + for (var i = start; i < end; i++) { + var byte = bytes[i]; + byteOr |= byte; + + // If the byte is an uppercase letter, convert it to lowercase to check if + // it's unreserved. This works because uppercase letters in ASCII are + // exactly `0b100000 = 0x20` less than lowercase letters, so if we ensure + // that that bit is 1 we ensure that the letter is lowercase. + var letter = 0x20 | byte; + if ((letter >= $a && letter <= $z) || + (byte >= $0 && byte <= $9) || + byte == $dash || + byte == $dot || + byte == $underscore || + byte == $tilde) { + // Unreserved characters are safe to write as-is. + buffer.writeCharCode(byte); + continue; + } + + buffer.writeCharCode($percent); + + // The bitwise arithmetic here is equivalent to `byte ~/ 16` and `byte % 16` + // for valid byte values, but is easier for dart2js to optimize given that + // it can't prove that [byte] will always be positive. + buffer.writeCharCode(_codeUnitForDigit((byte & 0xF0) >> 4)); + buffer.writeCharCode(_codeUnitForDigit(byte & 0x0F)); + } + + if (byteOr >= 0 && byteOr <= 255) return buffer.toString(); + + // If there was an invalid byte, find it and throw an exception. + for (var i = start; i < end; i++) { + var byte = bytes[i]; + if (byte >= 0 && byte <= 0xff) continue; + throw FormatException( + "Invalid byte ${byte < 0 ? "-" : ""}0x${byte.abs().toRadixString(16)}.", + bytes, + i); + } + + throw 'unreachable'; +} + +/// Returns the ASCII/Unicode code unit corresponding to the hexadecimal digit +/// [digit]. +int _codeUnitForDigit(int digit) => digit < 10 ? digit + $0 : digit + $A - 10; diff --git a/packages/convert/lib/src/string_accumulator_sink.dart b/packages/convert/lib/src/string_accumulator_sink.dart new file mode 100644 index 0000000..c8bdf75 --- /dev/null +++ b/packages/convert/lib/src/string_accumulator_sink.dart @@ -0,0 +1,46 @@ +// 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'; + +/// A sink that provides access to the concatenated strings passed to it. +/// +/// See also [StringConversionSink.withCallback]. +class StringAccumulatorSink extends StringConversionSinkBase { + /// The string accumulated so far. + String get string => _buffer.toString(); + final _buffer = StringBuffer(); + + /// Whether [close] has been called. + bool get isClosed => _isClosed; + var _isClosed = false; + + /// Empties [string]. + /// + /// This can be used to avoid double-processing data. + void clear() { + _buffer.clear(); + } + + void add(String chunk) { + if (_isClosed) { + throw StateError("Can't add to a closed sink."); + } + + _buffer.write(chunk); + } + + void addSlice(String chunk, int start, int end, bool isLast) { + if (_isClosed) { + throw StateError("Can't add to a closed sink."); + } + + _buffer.write(chunk.substring(start, end)); + if (isLast) _isClosed = true; + } + + void close() { + _isClosed = true; + } +} diff --git a/packages/convert/lib/src/utils.dart b/packages/convert/lib/src/utils.dart new file mode 100644 index 0000000..4001e3e --- /dev/null +++ b/packages/convert/lib/src/utils.dart @@ -0,0 +1,39 @@ +// Copyright (c) 2015, 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. + +library convert.utils; + +import 'package:charcode/ascii.dart'; + +/// Returns the digit (0 through 15) corresponding to the hexadecimal code unit +/// at index [i] in [codeUnits]. +/// +/// If the given code unit isn't valid hexadecimal, throws a [FormatException]. +int digitForCodeUnit(List codeUnits, int index) { + // If the code unit is a numeral, get its value. XOR works because 0 in ASCII + // is `0b110000` and the other numerals come after it in ascending order and + // take up at most four bits. + // + // We check for digits first because it ensures there's only a single branch + // for 10 out of 16 of the expected cases. We don't count the `digit >= 0` + // check because branch prediction will always work on it for valid data. + var codeUnit = codeUnits[index]; + var digit = $0 ^ codeUnit; + if (digit <= 9) { + if (digit >= 0) return digit; + } else { + // If the code unit is an uppercase letter, convert it to lowercase. This + // works because uppercase letters in ASCII are exactly `0b100000 = 0x20` + // less than lowercase letters, so if we ensure that that bit is 1 we ensure + // that the letter is lowercase. + var letter = 0x20 | codeUnit; + if ($a <= letter && letter <= $f) return letter - $a + 10; + } + + throw FormatException( + "Invalid hexadecimal code unit " + "U+${codeUnit.toRadixString(16).padLeft(4, '0')}.", + codeUnits, + index); +} diff --git a/packages/convert/pubspec.yaml b/packages/convert/pubspec.yaml new file mode 100644 index 0000000..8002b7c --- /dev/null +++ b/packages/convert/pubspec.yaml @@ -0,0 +1,16 @@ +name: convert +version: 2.1.2-dev +description: Utilities for converting between data representations. +author: Dart Team +homepage: https://github.com/dart-lang/convert + +environment: + sdk: '>=2.0.0 <3.0.0' + +dependencies: + charcode: ^1.1.0 + typed_data: ^1.1.0 + +dev_dependencies: + pedantic: ^1.0.0 + test: ^1.0.0 \ No newline at end of file diff --git a/packages/crypto/.gitignore b/packages/crypto/.gitignore new file mode 100644 index 0000000..79f51c3 --- /dev/null +++ b/packages/crypto/.gitignore @@ -0,0 +1,3 @@ +.dart_tool +.packages +pubspec.lock diff --git a/packages/crypto/.test_config b/packages/crypto/.test_config new file mode 100644 index 0000000..531426a --- /dev/null +++ b/packages/crypto/.test_config @@ -0,0 +1,5 @@ +{ + "test_package": { + "platforms": ["vm"] + } +} \ No newline at end of file diff --git a/packages/crypto/.travis.yml b/packages/crypto/.travis.yml new file mode 100644 index 0000000..8be7e2d --- /dev/null +++ b/packages/crypto/.travis.yml @@ -0,0 +1,24 @@ +language: dart + +dart: + - 2.0.0 + - dev + +dart_task: + - test: -p vm + - test: -p firefox -j 1 + - dartanalyzer: --fatal-warnings --fatal-infos . + +matrix: + include: + # Only validate formatting using the dev release + - dart: dev + dart_task: dartfmt + +# Only building master means that we don't run two builds for each pull request. +branches: + only: [master] + +cache: + directories: + - $HOME/.pub-cache diff --git a/packages/crypto/AUTHORS b/packages/crypto/AUTHORS new file mode 100644 index 0000000..e8063a8 --- /dev/null +++ b/packages/crypto/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. diff --git a/packages/crypto/CHANGELOG.md b/packages/crypto/CHANGELOG.md new file mode 100644 index 0000000..3e279ea --- /dev/null +++ b/packages/crypto/CHANGELOG.md @@ -0,0 +1,103 @@ +## 2.0.5 + +* Changed the max message size instead to 0x3ffffffffffff, which is the largest + portable value for both JS and the Dart VM. + +## 2.0.4 + +* Made max message size a BigNum instead of an int so that dart2js can compile + with crypto. + +## 2.0.3 + +* Updated SDK version to 2.0.0-dev.17.0 + +## 2.0.2+1 + +* Fix SDK constraint. + +## 2.0.2 + +* Prepare `HashSink` implementation for limiting integers to 64 bits in Dart + language. + +## 2.0.1 + +* Support `convert` 2.0.0. + +## 2.0.0 + +**Note**: There are no APIs in 2.0.0 that weren't also in 0.9.2. Packages that +would use 2.0.0 as a lower bound should use 0.9.2 instead—for example, `crypto: +">=0.9.2 <3.0.0"`. + +* `Hash` and `Hmac` no longer extend `ChunkedConverter`. + +## 1.1.1 + +* Properly close sinks passed to `Hash.startChunkedConversion()` when + `ByteConversionSink.close()` is called. + +## 1.1.0 + +* `Hmac` and `Hash` now extend the new `ChunkedConverter` class from + `dart:convert`. + +* Fix all strong mode warnings. + +## 1.0.0 + +* All APIs that were deprecated in 0.9.2 have been removed. No new APIs have + been added. Packages that would use 1.0.0 as a lower bound should use 0.9.2 + instead—for example, `crypto: ">=0.9.2 <2.0.0"`. + +## 0.9.2+1 + +* Avoid core library methods that don't work on dart2js. + +## 0.9.2 + +* `Hash`, `MD5`, `SHA1`, and `SHA256` now implement `Converter`. They convert + between `List`s and the new `Digest` class, which represents a hash + digest. The `Converter` APIs—`Hash.convert()` and + `Hash.startChunkedConversion`—should be used in preference to the old APIs, + which are now deprecated. + +* `SHA1`, `SHA256`, and `HMAC` have been renamed to `Sha1`, `Sha256`, and + `Hmac`, respectively. The old names still work, but are deprecated. + +* Top-level `sha1`, `sha256`, and `md5` fields have been added to make it easier + to use those hash algorithms without having to instantiate new instances. + +* Hashing now works correctly for input sizes up to 2^64 bytes. + +### Deprecations + +* `Hash.add`, `Hash.close`, and `Hash.newInstance` are deprecated. + `Hash.convert` should be used for hashing single values, and + `Hash.startChunkedConversion` should be used for hashing streamed values. + +* `SHA1` and `SHA256` are deprecated. Use the top-level `sha1` and `sha256` + fields instead. + +* While the `MD5` class is not deprecated, the `new MD5()` constructor is. Use + the top-level `md5` field instead. + +* `HMAC` is deprecated. Use `Hmac` instead. + +* `Base64Codec`, `Base64Encoder`, `Base64Decoder`, `Base64EncoderSink`, + `Base64DecoderSink`, and `BASE64` are deprecated. Use the Base64 APIs in + `dart:convert` instead. + +* `CryptoUtils` is deprecated. Use the Base64 APIs in `dart:convert` and the hex + APIs in the `convert` package instead. + +## 0.9.1 + +* Base64 convert returns an Uint8List +* Base64 codec and encoder can now take an encodePaddingCharacter +* Implement a Base64 codec similar to codecs in 'dart:convert' + +## 0.9.0 + +* ChangeLog starts here. diff --git a/packages/crypto/CONTRIBUTING.md b/packages/crypto/CONTRIBUTING.md new file mode 100644 index 0000000..6f5e0ea --- /dev/null +++ b/packages/crypto/CONTRIBUTING.md @@ -0,0 +1,33 @@ +Want to contribute? Great! First, read this page (including the small print at +the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. + +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. + +### File headers +All files in the project must start with the following header. + + // Copyright (c) 2015, 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. + +### The small print +Contributions made by corporations are covered by a different agreement than the +one above, the +[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). diff --git a/packages/crypto/LICENSE b/packages/crypto/LICENSE new file mode 100644 index 0000000..de31e1a --- /dev/null +++ b/packages/crypto/LICENSE @@ -0,0 +1,26 @@ +Copyright 2015, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/crypto/README.md b/packages/crypto/README.md new file mode 100644 index 0000000..c31a1b8 --- /dev/null +++ b/packages/crypto/README.md @@ -0,0 +1,113 @@ +# Cryptographic hashing functions for Dart + +A set of cryptographic hashing functions implemented in pure Dart + +The following hashing algorithms are supported: + +* SHA-1 +* SHA-256 +* MD5 +* HMAC (i.e. HMAC-MD5, HMAC-SHA1, HMAC-SHA256) + +## Usage + +### Digest on a single input + +To hash a list of bytes, invoke the [`convert`][convert] method on the +[`sha1`][sha1-obj], [`sha256`][sha256-obj] or [`md5`][md5-obj] +objects. + +```dart +import 'package:crypto/crypto.dart'; +import 'dart:convert'; // for the utf8.encode method + +void main() { + var bytes = utf8.encode("foobar"); // data being hashed + + var digest = sha1.convert(bytes); + + print("Digest as bytes: ${digest.bytes}"); + print("Digest as hex string: $digest"); +} +``` + +### Digest on chunked input + +If the input data is not available as a _single_ list of bytes, use +the chunked conversion approach. + +Invoke the [`startChunkedConversion`][startChunkedConversion] method +to create a sink for the input data. On the sink, invoke the `add` +method for each chunk of input data, and invoke the `close` method +when all the chunks have been added. The digest can then be retrieved +from the `Sink` used to create the input data sink. + +```dart +import 'dart:convert'; +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; + +void main() { + var firstChunk = utf8.encode("foo"); + var secondChunk = utf8.encode("bar"); + + var output = new AccumulatorSink(); + var input = sha1.startChunkedConversion(output); + input.add(firstChunk); + input.add(secondChunk); // call `add` for every chunk of input data + input.close(); + var digest = output.events.single; + + print("Digest as bytes: ${digest.bytes}"); + print("Digest as hex string: $digest"); +} +``` + +The above example uses the `AccumulatorSink` class that comes with the +_convert_ package. It is capable of accumulating multiple events, but +in this usage only a single `Digest` is added to it when the data sink's +`close` method is invoked. + +### HMAC + +Create an instance of the [`Hmac`][Hmac] class with the hash function +and secret key being used. The object can then be used like the other +hash calculating objects. + +```dart +import 'dart:convert'; +import 'package:crypto/crypto.dart'; + +void main() { + var key = utf8.encode('p@ssw0rd'); + var bytes = utf8.encode("foobar"); + + var hmacSha256 = new Hmac(sha256, key); // HMAC-SHA256 + var digest = hmacSha256.convert(bytes); + + print("HMAC digest as bytes: ${digest.bytes}"); + print("HMAC digest as hex string: $digest"); +} +``` + +## Disclaimer + +Support for this library is given as _best effort_. + +This library has not been reviewed or vetted by security professionals. + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[convert]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Hash/convert.html +[Digest]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Digest-class.html +[Hmac]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Hmac-class.html +[MD5]: https://www.dartdocs.org/documentation/crypto/latest/crypto/MD5-class.html +[Sha1]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Sha1-class.html +[Sha256]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Sha256-class.html +[md5-obj]: https://www.dartdocs.org/documentation/crypto/latest/crypto/md5.html +[sha1-obj]: https://www.dartdocs.org/documentation/crypto/latest/crypto/sha1.html +[sha256-obj]: https://www.dartdocs.org/documentation/crypto/latest/crypto/sha256.html +[startChunkedConversion]: https://www.dartdocs.org/documentation/crypto/latest/crypto/Hash/startChunkedConversion.html +[tracker]: https://github.com/dart-lang/crypto/issues diff --git a/packages/crypto/analysis_options.yaml b/packages/crypto/analysis_options.yaml new file mode 100644 index 0000000..0711aca --- /dev/null +++ b/packages/crypto/analysis_options.yaml @@ -0,0 +1,43 @@ +include: package:pedantic/analysis_options.yaml +analyzer: + strong-mode: + implicit-casts: false +linter: + rules: + - avoid_empty_else + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_types + - cancel_subscriptions + - constant_identifier_names + - control_flow_in_finally + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + - hash_and_equals + - implementation_imports + - iterable_contains_unrelated_type + - library_names + - library_prefixes + - list_remove_unrelated_type + - non_constant_identifier_names + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_generic_function_type_aliases + - prefer_is_not_empty + - slash_for_doc_comments + - test_types_in_equals + - throw_in_finally + - type_init_formals + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_new + - unrelated_type_equality_checks + - valid_regexps diff --git a/packages/crypto/lib/crypto.dart b/packages/crypto/lib/crypto.dart new file mode 100644 index 0000000..990349a --- /dev/null +++ b/packages/crypto/lib/crypto.dart @@ -0,0 +1,10 @@ +// Copyright (c) 2012, 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. + +export 'src/digest.dart'; +export 'src/hash.dart'; +export 'src/hmac.dart'; +export 'src/md5.dart'; +export 'src/sha1.dart'; +export 'src/sha256.dart'; diff --git a/packages/crypto/lib/src/digest.dart b/packages/crypto/lib/src/digest.dart new file mode 100644 index 0000000..1ba4762 --- /dev/null +++ b/packages/crypto/lib/src/digest.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2015, 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 'package:collection/collection.dart'; +import 'package:convert/convert.dart'; + +/// A message digest as computed by a `Hash` or `HMAC` function. +class Digest { + /// The message digest as an array of bytes. + final List bytes; + + Digest(this.bytes); + + /// Returns whether this is equal to another digest. + /// + /// This should be used instead of manual comparisons to avoid leaking + /// information via timing. + @override + bool operator ==(Object other) => + other is Digest && const ListEquality().equals(bytes, other.bytes); + + @override + int get hashCode => const ListEquality().hash(bytes); + + /// The message digest as a string of hexadecimal digits. + @override + String toString() => hex.encode(bytes); +} diff --git a/packages/crypto/lib/src/digest_sink.dart b/packages/crypto/lib/src/digest_sink.dart new file mode 100644 index 0000000..48c85f5 --- /dev/null +++ b/packages/crypto/lib/src/digest_sink.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2015, 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 'digest.dart'; + +/// A sink used to get a digest value out of `Hash.startChunkedConversion`. +class DigestSink extends Sink { + /// The value added to the sink, if any. + Digest get value { + assert(_value != null); + return _value; + } + + Digest _value; + + /// Adds [value] to the sink. + /// + /// Unlike most sinks, this may only be called once. + @override + void add(Digest value) { + assert(_value == null); + _value = value; + } + + @override + void close() { + assert(_value != null); + } +} diff --git a/packages/crypto/lib/src/hash.dart b/packages/crypto/lib/src/hash.dart new file mode 100644 index 0000000..62299d5 --- /dev/null +++ b/packages/crypto/lib/src/hash.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2015, 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 'digest.dart'; +import 'digest_sink.dart'; + +/// An interface for cryptographic hash functions. +/// +/// Every hash is a converter that takes a list of ints and returns a single +/// digest. When used in chunked mode, it will only ever add one digest to the +/// inner [Sink]. +abstract class Hash extends Converter, Digest> { + /// The internal block size of the hash in bytes. + /// + /// This is exposed for use by the `Hmac` class, which needs to know the block + /// size for the [Hash] it uses. + int get blockSize; + + const Hash(); + + @override + Digest convert(List data) { + var innerSink = DigestSink(); + var outerSink = startChunkedConversion(innerSink); + outerSink.add(data); + outerSink.close(); + return innerSink.value; + } + + @override + ByteConversionSink startChunkedConversion(Sink sink); +} diff --git a/packages/crypto/lib/src/hash_sink.dart b/packages/crypto/lib/src/hash_sink.dart new file mode 100644 index 0000000..434b99d --- /dev/null +++ b/packages/crypto/lib/src/hash_sink.dart @@ -0,0 +1,158 @@ +// Copyright (c) 2012, 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:typed_data'; + +import 'package:typed_data/typed_data.dart'; + +import 'digest.dart'; +import 'utils.dart'; + +/// A base class for [Sink] implementations for hash algorithms. +/// +/// Subclasses should override [updateHash] and [digest]. +abstract class HashSink implements Sink> { + /// The inner sink that this should forward to. + final Sink _sink; + + /// Whether the hash function operates on big-endian words. + final Endian _endian; + + /// The words in the current chunk. + /// + /// This is an instance variable to avoid re-allocating, but its data isn't + /// used across invocations of [_iterate]. + final Uint32List _currentChunk; + + /// Messages with more than 2^53-1 bits are not supported. (This is the + /// largest value that is representable on both JS and the Dart VM.) + /// So the maximum length in bytes is (2^53-1)/8. + static const _maxMessageLengthInBytes = 0x0003ffffffffffff; + + /// The length of the input data so far, in bytes. + int _lengthInBytes = 0; + + /// Data that has yet to be processed by the hash function. + final _pendingData = Uint8Buffer(); + + /// Whether [close] has been called. + bool _isClosed = false; + + /// The words in the current digest. + /// + /// This should be updated each time [updateHash] is called. + Uint32List get digest; + + /// Creates a new hash. + /// + /// [chunkSizeInWords] represents the size of the input chunks processed by + /// the algorithm, in terms of 32-bit words. + HashSink(this._sink, int chunkSizeInWords, {Endian endian = Endian.big}) + : _endian = endian, + _currentChunk = Uint32List(chunkSizeInWords); + + /// Runs a single iteration of the hash computation, updating [digest] with + /// the result. + /// + /// [chunk] is the current chunk, whose size is given by the + /// `chunkSizeInWords` parameter passed to the constructor. + void updateHash(Uint32List chunk); + + @override + void add(List data) { + if (_isClosed) throw StateError('Hash.add() called after close().'); + _lengthInBytes += data.length; + _pendingData.addAll(data); + _iterate(); + } + + @override + void close() { + if (_isClosed) return; + _isClosed = true; + + _finalizeData(); + _iterate(); + assert(_pendingData.isEmpty); + _sink.add(Digest(_byteDigest())); + _sink.close(); + } + + Uint8List _byteDigest() { + if (_endian == Endian.host) return digest.buffer.asUint8List(); + + var byteDigest = Uint8List(digest.lengthInBytes); + var byteData = byteDigest.buffer.asByteData(); + for (var i = 0; i < digest.length; i++) { + byteData.setUint32(i * bytesPerWord, digest[i]); + } + return byteDigest; + } + + /// Iterates through [_pendingData], updating the hash computation for each + /// chunk. + void _iterate() { + var pendingDataBytes = _pendingData.buffer.asByteData(); + var pendingDataChunks = _pendingData.length ~/ _currentChunk.lengthInBytes; + for (var i = 0; i < pendingDataChunks; i++) { + // Copy words from the pending data buffer into the current chunk buffer. + for (var j = 0; j < _currentChunk.length; j++) { + _currentChunk[j] = pendingDataBytes.getUint32( + i * _currentChunk.lengthInBytes + j * bytesPerWord, _endian); + } + + // Run the hash function on the current chunk. + updateHash(_currentChunk); + } + + // Remove all pending data up to the last clean chunk break. + _pendingData.removeRange( + 0, pendingDataChunks * _currentChunk.lengthInBytes); + } + + /// Finalizes [_pendingData]. + /// + /// This adds a 1 bit to the end of the message, and expands it with 0 bits to + /// pad it out. + void _finalizeData() { + // Pad out the data with 0x80, eight 0s, and as many more 0s as we need to + // land cleanly on a chunk boundary. + _pendingData.add(0x80); + var contentsLength = _lengthInBytes + 9; + var finalizedLength = _roundUp(contentsLength, _currentChunk.lengthInBytes); + for (var i = 0; i < finalizedLength - contentsLength; i++) { + _pendingData.add(0); + } + + if (_lengthInBytes > _maxMessageLengthInBytes) { + throw UnsupportedError( + 'Hashing is unsupported for messages with more than 2^53 bits.'); + } + + var lengthInBits = _lengthInBytes * bitsPerByte; + + // Add the full length of the input data as a 64-bit value at the end of the + // hash. + var offset = _pendingData.length; + _pendingData.addAll(Uint8List(8)); + var byteData = _pendingData.buffer.asByteData(); + + // We're essentially doing byteData.setUint64(offset, lengthInBits, _endian) + // here, but that method isn't supported on dart2js so we implement it + // manually instead. + var highBits = lengthInBits >> 32; + var lowBits = lengthInBits & mask32; + if (_endian == Endian.big) { + byteData.setUint32(offset, highBits, _endian); + byteData.setUint32(offset + bytesPerWord, lowBits, _endian); + } else { + byteData.setUint32(offset, lowBits, _endian); + byteData.setUint32(offset + bytesPerWord, highBits, _endian); + } + } + + /// Rounds [val] up to the next multiple of [n], as long as [n] is a power of + /// two. + int _roundUp(int val, int n) => (val + n - 1) & -n; +} diff --git a/packages/crypto/lib/src/hmac.dart b/packages/crypto/lib/src/hmac.dart new file mode 100644 index 0000000..bbb2a4d --- /dev/null +++ b/packages/crypto/lib/src/hmac.dart @@ -0,0 +1,107 @@ +// Copyright (c) 2012, 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 'digest.dart'; +import 'digest_sink.dart'; +import 'hash.dart'; + +/// An implementation of [keyed-hash method authentication codes][rfc]. +/// +/// [rfc]: https://tools.ietf.org/html/rfc2104 +/// +/// HMAC allows messages to be cryptographically authenticated using any +/// iterated cryptographic hash function. +class Hmac extends Converter, Digest> { + /// The hash function used to compute the authentication digest. + final Hash _hash; + + /// The secret key shared by the sender and the receiver. + final Uint8List _key; + + /// Create an [Hmac] object from a [Hash] and a binary key. + /// + /// The key should be a secret shared between the sender and receiver of the + /// message. + Hmac(Hash hash, List key) + : _hash = hash, + _key = Uint8List(hash.blockSize) { + // Hash the key if it's longer than the block size of the hash. + if (key.length > _hash.blockSize) key = _hash.convert(key).bytes; + + // If [key] is shorter than the block size, the rest of [_key] will be + // 0-padded. + _key.setRange(0, key.length, key); + } + + @override + Digest convert(List data) { + var innerSink = DigestSink(); + var outerSink = startChunkedConversion(innerSink); + outerSink.add(data); + outerSink.close(); + return innerSink.value; + } + + @override + ByteConversionSink startChunkedConversion(Sink sink) => + _HmacSink(sink, _hash, _key); +} + +/// The concrete implementation of the HMAC algorithm. +class _HmacSink extends ByteConversionSink { + /// The sink for the outer hash computation. + final ByteConversionSink _outerSink; + + /// The sink that [_innerSink]'s result will be added to when it's available. + final _innerResultSink = DigestSink(); + + /// The sink for the inner hash computation. + ByteConversionSink _innerSink; + + /// Whether [close] has been called. + bool _isClosed = false; + + _HmacSink(Sink sink, Hash hash, List key) + : _outerSink = hash.startChunkedConversion(sink) { + _innerSink = hash.startChunkedConversion(_innerResultSink); + + // Compute outer padding. + var padding = Uint8List(key.length); + for (var i = 0; i < padding.length; i++) { + padding[i] = 0x5c ^ key[i]; + } + _outerSink.add(padding); + + // Compute inner padding. + for (var i = 0; i < padding.length; i++) { + padding[i] = 0x36 ^ key[i]; + } + _innerSink.add(padding); + } + + @override + void add(List data) { + if (_isClosed) throw StateError('HMAC is closed'); + _innerSink.add(data); + } + + @override + void addSlice(List data, int start, int end, bool isLast) { + if (_isClosed) throw StateError('HMAC is closed'); + _innerSink.addSlice(data, start, end, isLast); + } + + @override + void close() { + if (_isClosed) return; + _isClosed = true; + + _innerSink.close(); + _outerSink.add(_innerResultSink.value.bytes); + _outerSink.close(); + } +} diff --git a/packages/crypto/lib/src/md5.dart b/packages/crypto/lib/src/md5.dart new file mode 100644 index 0000000..9874011 --- /dev/null +++ b/packages/crypto/lib/src/md5.dart @@ -0,0 +1,124 @@ +// Copyright (c) 2012, 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 'digest.dart'; +import 'hash.dart'; +import 'hash_sink.dart'; +import 'utils.dart'; + +/// An instance of [MD5]. +/// +/// This instance provides convenient access to the [MD5][rfc] hash function. +/// +/// [rfc]: https://tools.ietf.org/html/rfc1321 +/// +/// **Warning**: MD5 has known collisions and should only be used when required +/// for backwards compatibility. +final md5 = MD5._(); + +/// An implementation of the [MD5][rfc] hash function. +/// +/// [rfc]: https://tools.ietf.org/html/rfc1321 +/// +/// **Warning**: MD5 has known collisions and should only be used when required +/// for backwards compatibility. +/// +/// Note that it's almost always easier to use [md5] rather than creating a new +/// instance. +class MD5 extends Hash { + @override + final int blockSize = 16 * bytesPerWord; + + MD5._(); + + @override + ByteConversionSink startChunkedConversion(Sink sink) => + ByteConversionSink.from(_MD5Sink(sink)); +} + +/// Data from a non-linear mathematical function that functions as +/// reproducible noise. +const _noise = [ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, // + 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, + 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, + 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, + 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, + 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +]; + +/// Per-round shift amounts. +const _shiftAmounts = [ + 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 05, 09, 14, // + 20, 05, 09, 14, 20, 05, 09, 14, 20, 05, 09, 14, 20, 04, 11, 16, 23, 04, 11, + 16, 23, 04, 11, 16, 23, 04, 11, 16, 23, 06, 10, 15, 21, 06, 10, 15, 21, 06, + 10, 15, 21, 06, 10, 15, 21 +]; + +/// The concrete implementation of [MD5]. +/// +/// This is separate so that it can extend [HashSink] without leaking additional +/// public members. +class _MD5Sink extends HashSink { + @override + final digest = Uint32List(4); + + _MD5Sink(Sink sink) : super(sink, 16, endian: Endian.little) { + digest[0] = 0x67452301; + digest[1] = 0xefcdab89; + digest[2] = 0x98badcfe; + digest[3] = 0x10325476; + } + + @override + void updateHash(Uint32List chunk) { + assert(chunk.length == 16); + + var a = digest[0]; + var b = digest[1]; + var c = digest[2]; + var d = digest[3]; + + int e; + int f; + + for (var i = 0; i < 64; i++) { + if (i < 16) { + e = (b & c) | ((~b & mask32) & d); + f = i; + } else if (i < 32) { + e = (d & b) | ((~d & mask32) & c); + f = ((5 * i) + 1) % 16; + } else if (i < 48) { + e = b ^ c ^ d; + f = ((3 * i) + 5) % 16; + } else { + e = c ^ (b | (~d & mask32)); + f = (7 * i) % 16; + } + + var temp = d; + d = c; + c = b; + b = add32( + b, + rotl32(add32(add32(a, e), add32(_noise[i], chunk[f])), + _shiftAmounts[i])); + a = temp; + } + + digest[0] = add32(a, digest[0]); + digest[1] = add32(b, digest[1]); + digest[2] = add32(c, digest[2]); + digest[3] = add32(d, digest[3]); + } +} diff --git a/packages/crypto/lib/src/sha1.dart b/packages/crypto/lib/src/sha1.dart new file mode 100644 index 0000000..ed7615b --- /dev/null +++ b/packages/crypto/lib/src/sha1.dart @@ -0,0 +1,104 @@ +// Copyright (c) 2012, 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 'digest.dart'; +import 'hash.dart'; +import 'hash_sink.dart'; +import 'utils.dart'; + +/// An instance of [Sha1]. +/// +/// This instance provides convenient access to the [SHA-1][rfc] hash function. +/// +/// [rfc]: http://tools.ietf.org/html/rfc3174 +final sha1 = Sha1._(); + +/// An implementation of the [SHA-1][rfc] hash function. +/// +/// [rfc]: http://tools.ietf.org/html/rfc3174 +class Sha1 extends Hash { + @override + final int blockSize = 16 * bytesPerWord; + + Sha1._(); + + @override + ByteConversionSink startChunkedConversion(Sink sink) => + ByteConversionSink.from(_Sha1Sink(sink)); +} + +/// The concrete implementation of [Sha1]. +/// +/// This is separate so that it can extend [HashSink] without leaking additional +/// public memebers. +class _Sha1Sink extends HashSink { + @override + final digest = Uint32List(5); + + /// The sixteen words from the original chunk, extended to 80 words. + /// + /// This is an instance variable to avoid re-allocating, but its data isn't + /// used across invocations of [updateHash]. + final Uint32List _extended; + + _Sha1Sink(Sink sink) + : _extended = Uint32List(80), + super(sink, 16) { + digest[0] = 0x67452301; + digest[1] = 0xEFCDAB89; + digest[2] = 0x98BADCFE; + digest[3] = 0x10325476; + digest[4] = 0xC3D2E1F0; + } + + @override + void updateHash(Uint32List chunk) { + assert(chunk.length == 16); + + var a = digest[0]; + var b = digest[1]; + var c = digest[2]; + var d = digest[3]; + var e = digest[4]; + + for (var i = 0; i < 80; i++) { + if (i < 16) { + _extended[i] = chunk[i]; + } else { + _extended[i] = rotl32( + _extended[i - 3] ^ + _extended[i - 8] ^ + _extended[i - 14] ^ + _extended[i - 16], + 1); + } + + var newA = add32(add32(rotl32(a, 5), e), _extended[i]); + if (i < 20) { + newA = add32(add32(newA, (b & c) | (~b & d)), 0x5A827999); + } else if (i < 40) { + newA = add32(add32(newA, (b ^ c ^ d)), 0x6ED9EBA1); + } else if (i < 60) { + newA = add32(add32(newA, (b & c) | (b & d) | (c & d)), 0x8F1BBCDC); + } else { + newA = add32(add32(newA, b ^ c ^ d), 0xCA62C1D6); + } + + e = d; + d = c; + c = rotl32(b, 30); + b = a; + a = newA & mask32; + } + + digest[0] = add32(a, digest[0]); + digest[1] = add32(b, digest[1]); + digest[2] = add32(c, digest[2]); + digest[3] = add32(d, digest[3]); + digest[4] = add32(e, digest[4]); + } +} diff --git a/packages/crypto/lib/src/sha256.dart b/packages/crypto/lib/src/sha256.dart new file mode 100644 index 0000000..6f0e40b --- /dev/null +++ b/packages/crypto/lib/src/sha256.dart @@ -0,0 +1,141 @@ +// Copyright (c) 2012, 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 'digest.dart'; +import 'hash.dart'; +import 'hash_sink.dart'; +import 'utils.dart'; + +/// An instance of [Sha256]. +/// +/// This instance provides convenient access to the [Sha256][rfc] hash function. +/// +/// [rfc]: http://tools.ietf.org/html/rfc6234 +final sha256 = Sha256._(); + +/// An implementation of the [SHA-256][rfc] hash function. +/// +/// [rfc]: http://tools.ietf.org/html/rfc6234 +/// +/// Note that it's almost always easier to use [sha256] rather than creating a +/// new instance. +class Sha256 extends Hash { + @override + final int blockSize = 16 * bytesPerWord; + + Sha256._(); + + Sha256 newInstance() => Sha256._(); + + @override + ByteConversionSink startChunkedConversion(Sink sink) => + ByteConversionSink.from(_Sha256Sink(sink)); +} + +/// Data from a non-linear function that functions as reproducible noise. +const List _noise = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, // + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +]; + +/// The concrete implementation of [Sha256]. +/// +/// This is separate so that it can extend [HashSink] without leaking additional +/// public members. +class _Sha256Sink extends HashSink { + @override + final digest = Uint32List(8); + + /// The sixteen words from the original chunk, extended to 64 words. + /// + /// This is an instance variable to avoid re-allocating, but its data isn't + /// used across invocations of [updateHash]. + final Uint32List _extended; + + _Sha256Sink(Sink sink) + : _extended = Uint32List(64), + super(sink, 16) { + // Initial value of the hash parts. First 32 bits of the fractional parts + // of the square roots of the first 8 prime numbers. + digest[0] = 0x6a09e667; + digest[1] = 0xbb67ae85; + digest[2] = 0x3c6ef372; + digest[3] = 0xa54ff53a; + digest[4] = 0x510e527f; + digest[5] = 0x9b05688c; + digest[6] = 0x1f83d9ab; + digest[7] = 0x5be0cd19; + } + + // The following helper functions are taken directly from + // http://tools.ietf.org/html/rfc6234. + + int _rotr32(int n, int x) => (x >> n) | ((x << (32 - n)) & mask32); + int _ch(int x, int y, int z) => (x & y) ^ ((~x & mask32) & z); + int _maj(int x, int y, int z) => (x & y) ^ (x & z) ^ (y & z); + int _bsig0(int x) => _rotr32(2, x) ^ _rotr32(13, x) ^ _rotr32(22, x); + int _bsig1(int x) => _rotr32(6, x) ^ _rotr32(11, x) ^ _rotr32(25, x); + int _ssig0(int x) => _rotr32(7, x) ^ _rotr32(18, x) ^ (x >> 3); + int _ssig1(int x) => _rotr32(17, x) ^ _rotr32(19, x) ^ (x >> 10); + + @override + void updateHash(Uint32List chunk) { + assert(chunk.length == 16); + + // Prepare message schedule. + for (var i = 0; i < 16; i++) { + _extended[i] = chunk[i]; + } + for (var i = 16; i < 64; i++) { + _extended[i] = add32(add32(_ssig1(_extended[i - 2]), _extended[i - 7]), + add32(_ssig0(_extended[i - 15]), _extended[i - 16])); + } + + // Shuffle around the bits. + var a = digest[0]; + var b = digest[1]; + var c = digest[2]; + var d = digest[3]; + var e = digest[4]; + var f = digest[5]; + var g = digest[6]; + var h = digest[7]; + + for (var i = 0; i < 64; i++) { + var temp1 = add32(add32(h, _bsig1(e)), + add32(_ch(e, f, g), add32(_noise[i], _extended[i]))); + var temp2 = add32(_bsig0(a), _maj(a, b, c)); + h = g; + g = f; + f = e; + e = add32(d, temp1); + d = c; + c = b; + b = a; + a = add32(temp1, temp2); + } + + // Update hash values after iteration. + digest[0] = add32(a, digest[0]); + digest[1] = add32(b, digest[1]); + digest[2] = add32(c, digest[2]); + digest[3] = add32(d, digest[3]); + digest[4] = add32(e, digest[4]); + digest[5] = add32(f, digest[5]); + digest[6] = add32(g, digest[6]); + digest[7] = add32(h, digest[7]); + } +} diff --git a/packages/crypto/lib/src/utils.dart b/packages/crypto/lib/src/utils.dart new file mode 100644 index 0000000..9ac8efc --- /dev/null +++ b/packages/crypto/lib/src/utils.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2015, 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. + +/// A bitmask that limits an integer to 32 bits. +const mask32 = 0xFFFFFFFF; + +/// The number of bits in a byte. +const bitsPerByte = 8; + +/// The number of bytes in a 32-bit word. +const bytesPerWord = 4; + +/// Adds [x] and [y] with 32-bit overflow semantics. +int add32(int x, int y) => (x + y) & mask32; + +/// Bitwise rotates [val] to the left by [shift], obeying 32-bit overflow +/// semantics. +int rotl32(int val, int shift) { + var modShift = shift & 31; + return ((val << modShift) & mask32) | ((val & mask32) >> (32 - modShift)); +} diff --git a/packages/crypto/pubspec.yaml b/packages/crypto/pubspec.yaml new file mode 100644 index 0000000..1955238 --- /dev/null +++ b/packages/crypto/pubspec.yaml @@ -0,0 +1,17 @@ +name: crypto +version: 2.0.6 +author: Dart Team +description: Library of cryptographic functions. +homepage: https://www.github.com/dart-lang/crypto + +environment: + sdk: '>=2.0.0 <3.0.0' + +dependencies: + collection: '^1.0.0' + convert: '>=1.0.0 <3.0.0' + typed_data: '^1.0.0' + +dev_dependencies: + pedantic: ^1.0.0 + test: ^1.0.0 diff --git a/packages/dart-uuid/.gitignore b/packages/dart-uuid/.gitignore new file mode 100644 index 0000000..7963cf0 --- /dev/null +++ b/packages/dart-uuid/.gitignore @@ -0,0 +1,11 @@ +tests/packages +*pubspec.lock +.children +.project +packages/ +packages +.packages +*.iml +.idea/ +out/ +.dart_tool \ No newline at end of file diff --git a/packages/dart-uuid/.travis.yml b/packages/dart-uuid/.travis.yml new file mode 100644 index 0000000..fd4874a --- /dev/null +++ b/packages/dart-uuid/.travis.yml @@ -0,0 +1,6 @@ +language: dart +dart: + - dev + - stable + +sudo: false diff --git a/packages/dart-uuid/CHANGELOG.md b/packages/dart-uuid/CHANGELOG.md new file mode 100644 index 0000000..09a712a --- /dev/null +++ b/packages/dart-uuid/CHANGELOG.md @@ -0,0 +1,164 @@ +v2.0.2 +* Merge fix for time precision loss in V1 time based UUIDs. + + +v2.0.1 +* Fix regression where CryptoRNG was default, moved back to MathRNG +* Added ability to set RNG globally to skip having to set it in every function call +* Allows you to set the v1 clock sequence, nodeID, and seed bytes to use cryptoRNG separately from globalRNG. + + +v2.0.0 +* Fixup the API to split out Buffer and Non-buffer usages. +* Switch to build in Random.secure() and remove custom AES implementation. +* Less dependencies. +* Docs +* Cleanup + +v1.0.3 +* Fix SDK constraints to allow Dart 2.0 stable. + +v1.0.2 +* Fix constants breaking in Dart 1.x, need to be backwards compatible. + +v1.0.1 +* Fix constants to match Dart 2.0 spec + +v1.0.0 + +* Cleanup and prep for dart 2.0 +* Has been stable for a long time, upgrading to 1.0 version + +v0.5.3 + +* Merged pull request to support crypto 2.0.0 +* Support convert 2.0.0 + +v0.5.2 + +* Merged pull request to upgrade crypto library to 1.0.0. + +v0.5.1 + +* Merged pull request for various updates and cleanup. + +v0.5.0 + +* Reverted back to custom AES implementation. Moved RNG methods to UuidUtil (import 'package:uuid/uuid_util.dart') +* Fixed a potential bug with custom RNG method passing and added more ways to pass in custom RNG functions. +* Cleaned up and refactored some stuff. Using only v1 is only 67kb of js, Using only v4 is 97kb. Using crypt v4 is 118kb. Using both v1 and non-crypto v4 is 126kb. +* Default RNG for v4 is now the mathRNG function. If you wish to use cryptoRNG, import UuidUtil and pass in cryptoRNG. +* Updated README.md with more examples and usages. +* Updated Tests. + +v0.4.1 + +* Changed initCipher location so that if you ever only use v1 UUIDs, you will get a very small Dart2JS output compared to v4 or v5 that do load it. + +v0.4.0 + +* Use Cipher base.dart, as I don't need entropy generators, and this allows me to merge client/server together again + and fix many issues this caused. + +v0.3.2 + +* Fix import/library bug. + +v0.3.1 + +* Update pubspec to allow installation of the latest Cipher 0.7. + +v0.3.0 + +* Updated to latest Cipher at 0.6.0. This created a breaking change in the imports. Please make sure you update your code. +* Fixed problem when creating v4 UUIDs too fast, it would create duplicate UUIDs. + +v0.2.2 + +* Pegging cipher to 0.4.0 temporarily for browser support + +v0.2.1 + +* Using new version of cipher. + +v0.2.0 + +* Dart 1.0 Readiness +* Switched from custom AES to [cipher](https://github.com/izaera/cipher) package AES. + +v0.1.6 + +* Adjusting usage of constants. +* Fixing tests. + +v0.1.5 + +* Stupid typo on import. + +v0.1.4 + +* Fixing Crypto package move. + +v0.1.3 + +* Fixing language changes. + +v0.1.2 + +* Fix change of charCodes to codeUnits + +v0.1.1 + +* Fixing syntax for upcoming breaking changes. + +v0.1.0 + +* Cleanup, changes, and prep for M3. + +v0.0.9 + +* Minor fix with a const RegExp +* Made sure everything builds on latest dart. +* Fixed pubspec to now import unittest from pub instead of sdk. + +v0.0.8 + +* Changed to the new optional paramater syntaxes and usages. +* Adjusted tests for the new function call style due to parameter change. +* Fixed Import/Source/Library statements to the new format. + +v0.0.7 + +* Made changes requested by the Google team to get my package up on pub.dartlang.org + +v0.0.6 + +* Fixed up some code to make it possibly faster and using better Dart practices. +* Cleaned up some documentation. + +v0.0.5 + +* Added Initial AES for Dart (untested if it actually works/matches other AES encryptors) +* Use AES cipher to create crypto strong bytes. + +v0.0.4 + +* Issue wasn't Math.Random() but a bad reseed by me. +* Cleaned up for new Pub layout. + +v0.0.3 + +* Added UUIDv5 +* Fixed UUIDv4 bugs +* Added more unit tests +* Found bug in dart's Math.Random(), reported, waiting for fix to fix my code. + +v0.0.2 + +* Initial tests +* Fixed some parser bugs. + +v0.0.1 + +* Initial Release +* No tests diff --git a/packages/dart-uuid/LICENSE b/packages/dart-uuid/LICENSE new file mode 100644 index 0000000..178ba64 --- /dev/null +++ b/packages/dart-uuid/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2012 Yulian Kuncheff + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/dart-uuid/README.md b/packages/dart-uuid/README.md new file mode 100644 index 0000000..3f2c2cd --- /dev/null +++ b/packages/dart-uuid/README.md @@ -0,0 +1,277 @@ +[![Build Status](https://travis-ci.org/Daegalus/dart-uuid.svg?branch=master)](https://travis-ci.org/Daegalus/dart-uuid) + +# dart-uuid + +**Version 2.0.0 has breaking API changes. Please review them below.** + +Simple, fast generation of [RFC4122](http://www.ietf.org/rfc/rfc4122.txt) UUIDs. + +Originally based on node-uuid by Robert Kieffer (I even copied this readme over and modified it.) +Primarily because it works, well written, and so on. It has changed considerably since the start though. + +Features: + +* Generate RFC4122 version 1, version 4, or version 5 UUIDs +* Runs in web, server, and flutter +* Cryptographically strong random number generation on all platforms + * **Defaults to non-crypto generator, see UuidUtil for cryptoRNG** +* [Documentation](http://daegalus.github.com/dart-uuid/index.html) + +## Getting Started + +### Instructions + +1. Open a command line and cd to your projects root folder +2. In your pubspec, add an entry for dart-uuid to your dependencies (example below) +3. pub install +4. If you wish to run tests, go into packages/dart-uuid/ and run 'dart test/uuid_test.dart' + +### Pubspec + +There are 2 options. Directly from git, or from pub.dartlang.org + +pub.dartlang.org: (you can use 'any' instead of a version if you just want the latest always) + +```yaml +dependencies: + uuid: 2.0.2 +``` + +```dart +import 'package:uuid/uuid.dart'; + +var uuid = new Uuid(); +``` + +Then create some ids ... + +```dart +// Generate a v1 (time-based) id +uuid.v1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a' + +// Generate a v4 (random) id +uuid.v4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1' + +// Generate a v5 (namespace-name-sha1-based) id +uuid.v5(Uuid.NAMESPACE_URL, 'www.google.com'); // -> 'c74a196f-f19d-5ea9-bffd-a2742432fc9c' +``` + +## API + +### Uuid({Map options: null}) -> Uuid (Consturctor) + +Constructor supports setting some global RNG sertings so you don't have to specify them on each function call for v4 or v5 + +* `options` - (Map) Optional uuid state to apply. Properties may include: + + * `grng` - (Function) Random # generator to use as a global rng function. A Custom function that returns an list[16] of byte values or 1 of 2 provided. + * `gNamedArgs` - (Map) The arguments and values you want to pass to your global rng function. + * `gPositionalArgs` - (List) The positional arguments for your global rng functions, if any. + * `v1rng` - (Function) Random # generator to use as a rng function for v1 seed. A Custom function that returns an list[16] of byte values or 1 of 2 provided. + * `v1rngNamedArgs` - (Map) The arguments and values you want to pass to your v1 rng function. + * `v1rngPositionalArgs` - (List) The positional arguments for your v1 rng functions, if any. + +Defaults are `Uuid.mathRNG` + +Example: Using CryptoRNG globally + +```dart +var uuid = new Uuid(options: { + 'grng': UuidUtil.cryptoRNG +}) + +// Generate a v4 (random) id that will use cryptRNG for its rng function +uuid.v4(); +``` + +### uuid.v1({Map options: null) -> String +### uuid.v1buffer(List buffer, {Map options: null, int offset: 0}) -> List + +Generate and return a RFC4122 v1 (timestamp-based) UUID. + +* `options` - (Map) Optional uuid state to apply. Properties may include: + + * `node` - (List) Node id as List of 6 bytes (per 4.1.6). Default: Randomnly generated ID. + * `clockseq` - (Number between 0 - 0x3fff) RFC clock sequence. Default: An internally maintained clockseq is used. + * `msecs` - (Number) Time in milliseconds since unix Epoch. Default: The current time is used. + * `nsecs` - (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if `msecs` is unspecified. Default: internal uuid counter is used, as per 4.2.1.2. + +* `buffer` - (List) Array or buffer where UUID bytes are to be written. +* `offset` - (Int) Starting index in `buffer` at which to begin writing. + +v1() returns a string representation of the uuid. + +v1buffer() Returns a List `buffer`, if specified, also writes the data to the provided buffer. + +Example: Generate string UUID with fully-specified options + +```dart +uuid.v1(options: { + 'node': [0x01, 0x23, 0x45, 0x67, 0x89, 0xab], + 'clockSeq': 0x1234, + 'mSecs': new DateTime.utc(2011,11,01).millisecondsSinceEpoch, + 'nSecs': 5678 +}) // -> "710b962e-041c-11e1-9234-0123456789ab" +``` + +Example: In-place generation of two binary IDs + +```dart +// Generate two ids in an array +var myBuffer = new List(32); // -> [] +uuid.v1buffer(myBuffer); +// -> [115, 189, 5, 128, 201, 91, 17, 225, 146, 52, 109, 0, 9, 0, 52, 128, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null] +uuid.v1buffer(myBuffer, offset: 16); +// -> [115, 189, 5, 128, 201, 91, 17, 225, 146, 52, 109, 0, 9, 0, 52, 128, 115, 189, 5, 129, 201, 91, 17, 225, 146, 52, 109, 0, 9, 0, 52, 128] + +// Optionally use uuid.unparse() to get stringify the ids +uuid.unparse(myBuffer); // -> '73bd0580-c95b-11e1-9234-6d0009003480' +uuid.unparse(myBuffer, offset: 16) // -> '73bd0581-c95b-11e1-9234-6d0009003480' +``` + +### uuid.v4({Map options: null}) +### uuid.v4buffer(List buffer, {Map options: null, int offset: 0}) +Generate and return a RFC4122 v4 UUID. + +* `options` - (Map) Optional uuid state to apply. Properties may include: + + * `random` - (Number[16]) List of 16 numbers (0-255) to use in place of randomly generated values + * `rng` - (Function) Random # generator to use. A Custom function that returns an list[16] of byte values or 1 of 2 provided. + * `namedArgs` - (Map) The arguments and values you want to pass to your function. + * `positionalArgs` - (List) The positional arguments for your functions. if any. + +* `buffer` - (List) Array or buffer where UUID bytes are to be written. +* `offset` - (Number) Starting index in `buffer` at which to begin writing. + +v4() returns a string representation of the uuid. + +v4buffer() Returns a List `buffer`, if specified, also writes the data to the provided buffer. + +Example: Generate string UUID with different RNG method + +```dart +import 'package:uuid/uuid_util.dart'; +uuid.v4(options: { + 'rng': UuidUtil.cryptoRNG +}); +// -> "109156be-c4fb-41ea-b1b4-efe1671c5836" +``` + +Example: Generate string UUID with different RNG method and named parameters + +```dart +import 'package:uuid/uuid_util.dart'; +uuid.v4(options: { + 'rng': UuidUtil.mathRNG, + 'namedArgs': new Map.fromIterables([const Symbol('seed')],[1]) +}); +// -> "09a91894-e93f-4141-a3ec-82eb32f2a3ef" +``` + +Example: Generate string UUID with different RNG method and positional parameters + +```dart +import 'package:uuid/uuid_util.dart'; +uuid.v4(options: { + 'rng': UuidUtil.cryptoRNG, + 'positionalArgs': [1] +}); +// -> "09a91894-e93f-4141-a3ec-82eb32f2a3ef" +``` + +Example: Generate string UUID with fully-specified options + +```dart +uuid.v4(options: { + 'random': [ + 0x10, 0x91, 0x56, 0xbe, 0xc4, 0xfb, 0xc1, 0xea, + 0x71, 0xb4, 0xef, 0xe1, 0x67, 0x1c, 0x58, 0x36 + ] +}); +// -> "109156be-c4fb-41ea-b1b4-efe1671c5836" +``` + +Example: Generate two IDs in a single buffer + +```dart +var myBuffer = new List(32); +uuid.v4buffer(myBuffer); +uuid.v4buffer(myBuffer, offset: 16); +``` + +### uuid.v5(String namespace, String name, {Map options: null}) +### uuid.v5buffer(String namespace, String name, List buffer, {Map options: null, int offset: 0}) +Generate and return a RFC4122 v5 UUID. + +* `options` - (Map) Optional uuid state to apply. Properties may include: + + * `randomNamespace` - (Boolean) Default True. Returns if you want a v4 generated namespace (true) or NAMESPACE_NIL (false) + +* `buffer` - (List) Array or buffer where UUID bytes are to be written. +* `offset` - (Number) Starting index in `buffer` at which to begin writing. + +v5() returns a string representation of the uuid. + +v5buffer() Returns a List `buffer`, if specified, also writes the data to the provided buffer. + +Example: Generate string UUID with fully-specified options + +```dart +uuid.v5(Uuid.NAMESPACE_URL, 'www.google.com'); +// -> "c74a196f-f19d-5ea9-bffd-a2742432fc9c" +``` + +Example: Generate two IDs in a single buffer + +```dart +var myBuffer = new List(32); +uuid.v5buffer(Uuid.NAMESPACE_URL, 'www.google.com', myBuffer); +uuid.v5buffer(Uuid.NAMESPACE_URL, 'www.google.com', myBuffer, offset: 16); +``` + +### uuid.parse(String uuid, {List buffer: null, int offset: 0}) + +### uuid.unparse(List buffer, {int offset: 0}) + +Parse and unparse UUIDs + +* `id` - (String) UUID(-like) string +* `buffer` - (List) Array or buffer where UUID bytes are to be written. Default: A new Array or Buffer is used +* `offset` - (Int | Number) Starting index in `buffer` at which to begin writing. Default: 0 + +Example parsing and unparsing a UUID string + +```dart +var bytes = uuid.parse('797ff043-11eb-11e1-80d6-510998755d10'); // -> [121, 127, 240, 67, 17, 235, 17, 225, 128, 214, 81, 9, 152, 117, 93, 16] +var string = uuid.unparse(bytes); // -> '797ff043-11eb-11e1-80d6-510998755d10' +``` + +For more examples or usage, check my test implementations. + +## Testing + +In dartvm + +``` +dart test\uuid_test.dart +``` + +In Browser/flutter + +No in browser testing, but I know many use it in Web and Flutter projects. + +### Dart2js output size (minified, optimized with -O2) + +* v1 only: 56kb +* v4 only: 54kb +* v4 crypto only: 55kb +* v5 only: 67kb +* v1 + v4: 59kb (includes crypto) +* v4 + v5: 68kb (includes crypto) + +* ALL: 72kb +* v1 + v5: 70kb + +## Release notes + +See CHANGELOG diff --git a/packages/dart-uuid/analysis_options.yaml b/packages/dart-uuid/analysis_options.yaml new file mode 100644 index 0000000..15e2945 --- /dev/null +++ b/packages/dart-uuid/analysis_options.yaml @@ -0,0 +1,19 @@ +include: package:pedantic/analysis_options.yaml + +linter: + rules: + - always_declare_return_types + - camel_case_types + - empty_constructor_bodies + - annotate_overrides + - avoid_init_to_null + - one_member_abstracts + - slash_for_doc_comments + - unnecessary_brace_in_string_interps + +analyzer: + errors: + invalid_assignment: warning + missing_return: error + dead_code: info + todo: info \ No newline at end of file diff --git a/packages/dart-uuid/lib/uuid.dart b/packages/dart-uuid/lib/uuid.dart new file mode 100644 index 0000000..f759052 --- /dev/null +++ b/packages/dart-uuid/lib/uuid.dart @@ -0,0 +1,364 @@ +library Uuid; + +import 'uuid_util.dart'; +// import 'package:crypto/crypto.dart'; +// import 'package:convert/convert.dart' as convert; + +/// uuid for Dart +/// Author: Yulian Kuncheff +/// Released under MIT License. + +class Uuid { + // RFC4122 provided namespaces for v3 and v5 namespace based UUIDs + static const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; + static const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; + static const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'; + static const NAMESPACE_X500 = '6ba7b814-9dad-11d1-80b4-00c04fd430c8'; + static const NAMESPACE_NIL = '00000000-0000-0000-0000-000000000000'; + + var _seedBytes, _nodeId, _clockSeq, _lastMSecs = 0, _lastNSecs = 0; + Function _globalRNG; + List _byteToHex; + Map _hexToByte; + + Uuid({Map options}) { + options = (options != null) ? options : new Map(); + _byteToHex = new List(256); + _hexToByte = new Map(); + + // Easy number <-> hex conversion + for (var i = 0; i < 256; i++) { + var hex = new List(); + hex.add(i); + _byteToHex[i] = convert.hex.encode(hex); + _hexToByte[_byteToHex[i]] = i; + } + + // Sets initial seedBytes, node, and clock seq based on mathRNG. + var v1PositionalArgs = (options['v1rngPositionalArgs'] != null) + ? options['v1rngPositionalArgs'] + : []; + var v1NamedArgs = (options['v1rngNamedArgs'] != null) + ? options['v1rngNamedArgs'] as Map + : const {}; + _seedBytes = (options['v1rng'] != null) + ? Function.apply(options['v1rng'], v1PositionalArgs, v1NamedArgs) + : UuidUtil.mathRNG(); + + // Set the globalRNG function to mathRNG with the option to set an alternative globally + var gPositionalArgs = (options['grngPositionalArgs'] != null) + ? options['grngPositionalArgs'] + : []; + var gNamedArgs = (options['grngNamedArgs'] != null) + ? options['grngNamedArgs'] as Map + : const {}; + _globalRNG = () { + return (options['grng'] != null) + ? Function.apply(options['grng'], gPositionalArgs, gNamedArgs) + : UuidUtil.mathRNG(); + }; + + // Per 4.5, create a 48-bit node id (47 random bits + multicast bit = 1) + _nodeId = [ + _seedBytes[0] | 0x01, + _seedBytes[1], + _seedBytes[2], + _seedBytes[3], + _seedBytes[4], + _seedBytes[5] + ]; + + // Per 4.2.2, randomize (14 bit) clockseq + _clockSeq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3ffff; + } + + ///Parses the provided [uuid] into a list of byte values. + /// Can optionally be provided a [buffer] to write into and + /// a positional [offset] for where to start inputting into the buffer. + List parse(String uuid, {List buffer, int offset = 0}) { + var i = offset, ii = 0; + + // Create a 16 item buffer if one hasn't been provided. + buffer = (buffer != null) ? buffer : new List(16); + + // Convert to lowercase and replace all hex with bytes then + // string.replaceAll() does a lot of work that I don't need, and a manual + // regex gives me more control. + final RegExp regex = new RegExp('[0-9a-f]{2}'); + for (Match match in regex.allMatches(uuid.toLowerCase())) { + if (ii < 16) { + var hex = uuid.toLowerCase().substring(match.start, match.end); + buffer[i + ii++] = _hexToByte[hex]; + } + } + + // Zero out any left over bytes if the string was too short. + while (ii < 16) { + buffer[i + ii++] = 0; + } + + return buffer; + } + + /// Unparses a [buffer] of bytes and outputs a proper UUID string. + /// An optional [offset] is allowed if you want to start at a different point + /// in the buffer. + String unparse(List buffer, {int offset = 0}) { + var i = offset; + return '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}' + '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-' + '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-' + '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-' + '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}-' + '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}' + '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}' + '${_byteToHex[buffer[i++]]}${_byteToHex[buffer[i++]]}'; + } + + /// v1() Generates a time-based version 1 UUID + /// + /// By default it will generate a string based off current time, and will + /// return a string. + /// + /// If an optional [buffer] list is provided, it will put the byte data into + /// that buffer and return a buffer. + /// + /// Optionally an [offset] can be provided with a start position in the buffer. + /// + /// The first argument is an options map that takes various configuration + /// options detailed in the readme. + /// + /// http://tools.ietf.org/html/rfc4122.html#section-4.2.2 + String v1({Map options}) { + var i = 0; + var buf = new List(16); + options = (options != null) ? options : new Map(); + + var clockSeq = + (options['clockSeq'] != null) ? options['clockSeq'] : _clockSeq; + + // UUID timestamps are 100 nano-second units since the Gregorian epoch, + // (1582-10-15 00:00). Time is handled internally as 'msecs' (integer + // milliseconds) and 'nsecs' (100-nanoseconds offset from msecs) since unix + // epoch, 1970-01-01 00:00. + var mSecs = (options['mSecs'] != null) + ? options['mSecs'] + : (new DateTime.now()).millisecondsSinceEpoch; + + // Per 4.2.1.2, use count of uuid's generated during the current clock + // cycle to simulate higher resolution clock + var nSecs = (options['nSecs'] != null) ? options['nSecs'] : _lastNSecs + 1; + + // Time since last uuid creation (in msecs) + var dt = (mSecs - _lastMSecs) + (nSecs - _lastNSecs) / 10000; + + // Per 4.2.1.2, Bump clockseq on clock regression + if (dt < 0 && options['clockSeq'] == null) { + clockSeq = clockSeq + 1 & 0x3fff; + } + + // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new + // time interval + if ((dt < 0 || mSecs > _lastMSecs) && options['nSecs'] == null) { + nSecs = 0; + } + + // Per 4.2.1.2 Throw error if too many uuids are requested + if (nSecs >= 10000) { + throw new Exception('uuid.v1(): Can\'t create more than 10M uuids/sec'); + } + + _lastMSecs = mSecs; + _lastNSecs = nSecs; + _clockSeq = clockSeq; + + // Per 4.1.4 - Convert from unix epoch to Gregorian epoch + mSecs += 12219292800000; + + // time Low + var tl = ((mSecs & 0xfffffff) * 10000 + nSecs) % 0x100000000; + buf[i++] = tl >> 24 & 0xff; + buf[i++] = tl >> 16 & 0xff; + buf[i++] = tl >> 8 & 0xff; + buf[i++] = tl & 0xff; + + // time mid + var tmh = (mSecs / 0x100000000 * 10000).floor() & 0xfffffff; + buf[i++] = tmh >> 8 & 0xff; + buf[i++] = tmh & 0xff; + + // time high and version + buf[i++] = tmh >> 24 & 0xf | 0x10; // include version + buf[i++] = tmh >> 16 & 0xff; + + // clockSeq high and reserved (Per 4.2.2 - include variant) + buf[i++] = clockSeq >> 8 | 0x80; + + // clockSeq low + buf[i++] = clockSeq & 0xff; + + // node + var node = (options['node'] != null) ? options['node'] : _nodeId; + for (var n = 0; n < 6; n++) { + buf[i + n] = node[n]; + } + + return unparse(buf); + } + + /// v1buffer() Generates a time-based version 1 UUID + /// + /// By default it will generate a string based off current time, and will + /// place the result into the provided [buffer]. The [buffer] will also be returned.. + /// + /// Optionally an [offset] can be provided with a start position in the buffer. + /// + /// The first argument is an options map that takes various configuration + /// options detailed in the readme. + /// + /// http://tools.ietf.org/html/rfc4122.html#section-4.2.2 + List v1buffer( + List buffer, { + Map options, + int offset = 0, + }) { + var _buf = parse(v1(options: options)); + + if (buffer != null) { + buffer.setRange(offset, offset + 16, _buf); + } + + return buffer; + } + + /// v4() Generates a RNG version 4 UUID + /// + /// By default it will generate a string based mathRNG, and will return + /// a string. If you wish to use crypto-strong RNG, pass in UuidUtil.cryptoRNG + /// + /// The first argument is an options map that takes various configuration + /// options detailed in the readme. + /// + /// http://tools.ietf.org/html/rfc4122.html#section-4.4 + String v4({Map options}) { + options = (options != null) ? options : new Map(); + + // Use the built-in RNG or a custom provided RNG + var positionalArgs = + (options['positionalArgs'] != null) ? options['positionalArgs'] : []; + var namedArgs = (options['namedArgs'] != null) + ? options['namedArgs'] as Map + : const {}; + var rng = (options['rng'] != null) + ? Function.apply(options['rng'], positionalArgs, namedArgs) + : _globalRNG(); + + // Use provided values over RNG + var rnds = (options['random'] != null) ? options['random'] : rng; + + // per 4.4, set bits for version and clockSeq high and reserved + rnds[6] = (rnds[6] & 0x0f) | 0x40; + rnds[8] = (rnds[8] & 0x3f) | 0x80; + + return unparse(rnds); + } + + /// v4buffer() Generates a RNG version 4 UUID + /// + /// By default it will generate a string based off mathRNG, and will + /// place the result into the provided [buffer]. The [buffer] will also be returned. + /// If you wish to have crypto-strong RNG, pass in UuidUtil.cryptoRNG. + /// + /// Optionally an [offset] can be provided with a start position in the buffer. + /// + /// The first argument is an options map that takes various configuration + /// options detailed in the readme. + /// + /// http://tools.ietf.org/html/rfc4122.html#section-4.4 + List v4buffer( + List buffer, { + Map options, + int offset = 0, + }) { + var _buf = parse(v4(options: options)); + + if (buffer != null) { + buffer.setRange(offset, offset + 16, _buf); + } + + return buffer; + } + + /// v5() Generates a namspace & name-based version 5 UUID + /// + /// By default it will generate a string based on a provided uuid namespace and + /// name, and will return a string. + /// + /// The first argument is an options map that takes various configuration + /// options detailed in the readme. + /// + /// http://tools.ietf.org/html/rfc4122.html#section-4.4 + String v5(String namespace, String name, {Map options}) { + options = (options != null) ? options : new Map(); + + // Check if user wants a random namespace generated by v4() or a NIL namespace. + var useRandom = (options['randomNamespace'] != null) + ? options['randomNamespace'] + : true; + + // If useRandom is true, generate UUIDv4, else use NIL + var blankNS = useRandom ? v4() : NAMESPACE_NIL; + + // Use provided namespace, or use whatever is decided by options. + namespace = (namespace != null) ? namespace : blankNS; + + // Use provided name, + name = (name != null) ? name : ''; + + // Convert namespace UUID to Byte List + var bytes = parse(namespace); + + // Convert name to a list of bytes + var nameBytes = new List(); + for (var singleChar in name.codeUnits) { + nameBytes.add(singleChar); + } + + // Generate SHA1 using namespace concatenated with name + List hashBytes = + sha1.convert(new List.from(bytes)..addAll(nameBytes)).bytes; + + // per 4.4, set bits for version and clockSeq high and reserved + hashBytes[6] = (hashBytes[6] & 0x0f) | 0x50; + hashBytes[8] = (hashBytes[8] & 0x3f) | 0x80; + + return unparse(hashBytes); + } + + /// v5buffer() Generates a RNG version 4 UUID + /// + /// By default it will generate a string based off current time, and will + /// place the result into the provided [buffer]. The [buffer] will also be returned.. + /// + /// Optionally an [offset] can be provided with a start position in the buffer. + /// + /// The first argument is an options map that takes various configuration + /// options detailed in the readme. + /// + /// http://tools.ietf.org/html/rfc4122.html#section-4.4 + List v5buffer( + String namespace, + String name, + List buffer, { + Map options, + int offset = 0, + }) { + var _buf = parse(v5(namespace, name, options: options)); + + if (buffer != null) { + buffer.setRange(offset, offset + 16, _buf); + } + + return buffer; + } +} diff --git a/packages/dart-uuid/lib/uuid_util.dart b/packages/dart-uuid/lib/uuid_util.dart new file mode 100644 index 0000000..7092ca8 --- /dev/null +++ b/packages/dart-uuid/lib/uuid_util.dart @@ -0,0 +1,30 @@ +library UuidUtil; + +import 'dart:math'; + +class UuidUtil { + /// Math.Random()-based RNG. All platforms, fast, not cryptographically strong. Optional Seed passable. + static List mathRNG({int seed = -1}) { + var rand, b = new List(16); + + var _rand = (seed == -1) ? new Random() : new Random(seed); + for (var i = 0; i < 16; i++) { + if ((i & 0x03) == 0) { + rand = (_rand.nextDouble() * 0x100000000).floor().toInt(); + } + b[i] = rand >> ((i & 0x03) << 3) & 0xff; + } + + return b; + } + + /// Crypto-Strong RNG. All platforms, unknown speed, cryptographically strong (theoretically) + static List cryptoRNG() { + var b = new List(16); + var rand = Random.secure(); + for (var i = 0; i < 16; i++) { + b[i] = rand.nextInt(1 << 8); + } + return b; + } +} diff --git a/packages/dart-uuid/lib/uuidv4.dart b/packages/dart-uuid/lib/uuidv4.dart new file mode 100644 index 0000000..0705046 --- /dev/null +++ b/packages/dart-uuid/lib/uuidv4.dart @@ -0,0 +1,37 @@ + +class Uuidv4 { + + + /// v4() Generates a RNG version 4 UUID + /// + /// By default it will generate a string based mathRNG, and will return + /// a string. If you wish to use crypto-strong RNG, pass in UuidUtil.cryptoRNG + /// + /// The first argument is an options map that takes various configuration + /// options detailed in the readme. + /// + /// http://tools.ietf.org/html/rfc4122.html#section-4.4 + String v4({Map options}) { + options = (options != null) ? options : new Map(); + + // Use the built-in RNG or a custom provided RNG + var positionalArgs = + (options['positionalArgs'] != null) ? options['positionalArgs'] : []; + var namedArgs = (options['namedArgs'] != null) + ? options['namedArgs'] as Map + : const {}; + var rng = (options['rng'] != null) + ? Function.apply(options['rng'], positionalArgs, namedArgs) + : _globalRNG(); + + // Use provided values over RNG + var rnds = (options['random'] != null) ? options['random'] : rng; + + // per 4.4, set bits for version and clockSeq high and reserved + rnds[6] = (rnds[6] & 0x0f) | 0x40; + rnds[8] = (rnds[8] & 0x3f) | 0x80; + + return unparse(rnds); + } + +} \ No newline at end of file diff --git a/packages/dart-uuid/pubspec.yaml b/packages/dart-uuid/pubspec.yaml new file mode 100644 index 0000000..a683f4b --- /dev/null +++ b/packages/dart-uuid/pubspec.yaml @@ -0,0 +1,14 @@ +name: uuid +version: 2.0.2 +description: > + RFC4122 (v1, v4, v5) UUID Generator and Parser for all Dart platforms (Web, VM, Flutter) +author: Yulian Kuncheff +homepage: https://github.com/Daegalus/dart-uuid +environment: + sdk: '>=2.0.0 <3.0.0' +dependencies: + crypto: '>=2.0.0 <3.0.0' + convert: '>=2.0.0 <3.0.0' +dev_dependencies: + pedantic: '>=1.0.0' + test: any diff --git a/packages/dart-uuid/test.html b/packages/dart-uuid/test.html new file mode 100644 index 0000000..e4dcef5 --- /dev/null +++ b/packages/dart-uuid/test.html @@ -0,0 +1,6 @@ + + + + + +