Skip to content

Commit

Permalink
#22, #14 - happy_hash
Browse files Browse the repository at this point in the history
- new randomString()
- hashedAll() logic upgrade
- new optional prefixLibrary flag (default is true)
- unit tests
  • Loading branch information
turn-a-round committed May 18, 2020
1 parent f9ef811 commit 0d03030
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 11 deletions.
28 changes: 21 additions & 7 deletions src/sprightly/lib/utils/happy_hash.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ enum HashLibrary {
hmac_sha512
}

final Random _random = Random.secure();

String hashedAll(
List<String> items, {
int hashLength = 16,
HashLibrary library = HashLibrary.sha1,
String key,
bool prefixLibrary = true,
}) {
key ??= (Random().nextInt(1000000000) + DateTime.now().millisecondsSinceEpoch)
.toString();
final utf8Key = utf8.encode(key);
final keyLength =
null == hashLength ? 256 : hashLength < 4096 ? hashLength : 4096;
final _key = key ?? randomString(keyLength);
final utf8Key = utf8.encode(_key);
final sink = AccumulatorSink<Digest>();
final byteChunks = items.map((str) => utf8.encode(str));
ByteConversionSink chunks;
Expand Down Expand Up @@ -79,12 +83,14 @@ String hashedAll(
}
byteChunks.forEach((bt) => chunks.add(bt));
chunks.close();
var result = "${library.toString().split(".").last.replaceAll("_", "")}"
":${sink.events.single}";
var result =
"${prefixLibrary ? library.toString().split(".").last.replaceAll("_", "") + ':' : ''}"
"${sink.events.single}";
if (null == hashLength) return result;
if (result.length < hashLength) {
final repeater = hashed(result + key, hashLength: null, library: library);
while (result.length < hashLength) result += repeater;
result += _key;
if (result.length < hashLength)
result += randomString(hashLength - result.length);
}
return result.substring(0, hashLength);
}
Expand All @@ -94,10 +100,18 @@ String hashed(
int hashLength = 16,
HashLibrary library = HashLibrary.sha1,
String key,
bool prefixLibrary = true,
}) =>
hashedAll(
[item],
hashLength: hashLength,
library: library,
key: key,
prefixLibrary: prefixLibrary,
);

String randomString([int length = 16]) {
final unicodeIntegers =
List<int>.generate(length, (index) => _random.nextInt(256));
return base64Encode(unicodeIntegers).substring(0, length);
}
97 changes: 93 additions & 4 deletions src/sprightly/test/utils/happy_hash_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ void main() {
String item = WordPair.random().asString;
List<String> items =
List<String>.generate(10, (i) => WordPair.random().asString);
var hashLengths = List<int>.generate(4, (i) => Random().nextInt(pow(10, i)));
var hashLengths =
List<int>.generate(4, (i) => Random.secure().nextInt(pow(10, i)));

HashLibrary.values.forEach((library) {
libraryName = library.toString().split('.').last;
isHmac = libraryName.startsWith('hmac_');
final prefix = library.toString().split(".").last.replaceAll("_", "") + ':';

group('happy hash: library: $libraryName', () {
if (!isHmac) {
Expand All @@ -28,6 +30,7 @@ void main() {
});
test('hashed(): length check', () {
hashLengths.forEach((hashLength) {
print('check with length: $hashLength');
// act
var result = hashed(item, hashLength: hashLength, library: library);

Expand Down Expand Up @@ -56,9 +59,19 @@ void main() {
});
} else {
group('With key', () {
var key = (Random().nextInt(1000000000) +
DateTime.now().millisecondsSinceEpoch)
.toString();
var key = 'k4q2d8CRLDXpxfBPic6xGkqCnm7Bk2bi9S2G7cB0eQTR3'
'SE15ap789aUcdqbI5tXfv+H0bxsHxTtCYhWwd3I27fcPJ'
'9MGOnUF6ukr/Mjt/4y+6v+qUuqvqowLFlXal1kLsE2Q2H'
'GyUzCPeiWZUuih9XyA9rbMRditEgmp3/+jJzjIPu0juwx'
'NXFiw/TnZroLeGTiyod1u54qRRi9EhYgIJAtV0iOQbrYj'
'Ujm31Z80hRrHO/eX2DKgB55XKZY+AQbGS5zKXntlA6war'
'mcR2bGfbRct0ZBIq92PvRDfKcSR9tlM7/JPNX99uiXLCQ'
'uHqsh5Ys4JvLBfqOv4bmI7SX+al5fiTmCermTFnmV9IU3'
'DgYq3rUb46TxeQBpWoF7LjmQ52af0F5ZwqeFLjADB4jE8'
'uIt+Scj1qaIBBE87gq16sWs1ig6DGhBkanS52LMWU/jG5'
'pnkOjtZ/CA/ykYNAlV6rEAhH1FUhY/wT+pKJ9lf4/2BbF'
'+MF0mGJcZ2hjiaef8nGTK5v3h5j42N3gonv26lDmRXOxA'
'P3XDs7esVNqonHsI1XAdVYRun';

test('hashed(): Consistency check', () {
// act
Expand All @@ -70,6 +83,7 @@ void main() {
});
test('hashed(): length check', () {
hashLengths.forEach((hashLength) {
print('check with length: $hashLength');
// act
var result = hashed(item,
hashLength: hashLength, library: library, key: key);
Expand Down Expand Up @@ -138,6 +152,81 @@ void main() {
});
});
}
test('hashed(): prefixLibrary check', () {
// act
final resultWith = hashed(item, library: library);
final resultWithout =
hashed(item, library: library, prefixLibrary: false);

// assert
expect(resultWith.startsWith(prefix), isTrue);
expect(resultWithout.startsWith(prefix), isFalse);
});
test('hashedAll(): prefixLibrary check', () {
// act
final resultWith = hashedAll(items, library: library);
final resultWithout =
hashedAll(items, library: library, prefixLibrary: false);

// assert
expect(resultWith.startsWith(prefix), isTrue);
expect(resultWithout.startsWith(prefix), isFalse);
});
});
});
group('happy hash: randomString()', () {
test('check with default length', () {
print('check with length: 16');
// arrange & act
final count = 50;
final hashLength = 16;
final result = List<String>.generate(count, (index) => randomString());
final set = Set<String>.from(result);
print('sample: ${result[0]}');

// assert
expect(result.length, set.length);
set.forEach((element) {
expect(element.length, hashLength);
});
});
hashLengths.forEach((hashLength) {
test('check with length', () {
print('check with length: $hashLength');
// arrange & act
final count = 50;
final result =
List<String>.generate(count, (index) => randomString(hashLength));
final set = Set<String>.from(result);
print('sample: ${result[0]}');

// assert
if (hashLength == 0) {
expect(result.length, count);
expect(set.length, 1);
} else {
expect(result.length, set.length);
}
set.forEach((element) {
expect(element.length, hashLength);
});
});
});
test('check with really long length', () {
print('check with length: 4096');
// arrange & act
final count = 50;
final hashLength = 4096;
final result =
List<String>.generate(count, (index) => randomString(hashLength));
final set = Set<String>.from(result);
print('sample: ${result[0]}');

// assert
expect(result.length, set.length);
set.forEach((element) {
expect(element.length, hashLength);
});
});
});
}

0 comments on commit 0d03030

Please sign in to comment.