Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions pkg/_pub_shared/lib/format/encoding.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) 2024, 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';

/// Base64 encodes [dataPoints] as 32-bit unsigned integers serialized with
/// little endian byte order.
String encodeIntsAsLittleEndianBase64String(List<int> dataPoints) {
final byteData = ByteData(4 * dataPoints.length);
for (int i = 0; i < dataPoints.length; i++) {
byteData.setUint32(4 * i, dataPoints[i], Endian.little);
}
return base64Encode(byteData.buffer.asUint8List());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just use the Uint32List constructor. Give it a length and use setAll

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@szakarias I'd suggest:

  return base64.encode(Uint8List.sublistView(Uint32List.fromList(dataPoints)));

Or

  return base64.encode(Uint32List.fromList(dataPoints).buffer.asUint8List());

But I'd advise against getting used to using .buffer.asUint8List(). Because when you have a Uint32List the underlying buffer could be longer than the contents of the actual Uint32List.

So technically, you'd have to read the documentation for the Uint32List constructor to see that it says:

The list is backed by a ByteBuffer containing precisely length times 4 bytes.

Relying on this promise it is safe to use .buffer.asUint8List().

If we had gotten the Uint32List from somewhere else, we'd have to read offsetInBytes and lengthInBytes and pass them when doing .buffer.asUint8List().

Hence, why I'd generally causing against accessing .buffer directly, because unless you know where the buffer was created, the location of your data within the buffer is not guaranteed. But Uint8List.sublistView will avoid such concerns.

}

/// Counter part to [encodeIntsAsLittleEndianBase64String].
List<int> decodeIntsFromLittleEndianBase64String(String encoded) {
final bytes = base64Decode(encoded);
final resLength = bytes.length ~/ 4;
final dataPoints = List.filled(resLength, -1);
final sublist = ByteData.sublistView(bytes);
for (int i = 0; i < resLength; i++) {
dataPoints[i] = sublist.getUint32(4 * i, Endian.little);
}
return dataPoints;
}
29 changes: 29 additions & 0 deletions pkg/_pub_shared/test/format/encoding_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2024, 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:_pub_shared/format/encoding.dart';
import 'package:test/test.dart';

void main() {
test('encode/decode success', () {
final data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
final encoded = encodeIntsAsLittleEndianBase64String(data);
expect(encoded, 'AQAAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA');
expect(decodeIntsFromLittleEndianBase64String(encoded), data);
});

test('encode/decode empty', () {
final data = <int>[];
final encoded = encodeIntsAsLittleEndianBase64String(data);
expect(encoded, '');
expect(decodeIntsFromLittleEndianBase64String(encoded), data);
});

test('encode/decode failure with negative integers', () {
final data = <int>[-1, -2];
final encoded = encodeIntsAsLittleEndianBase64String(data);
expect(encoded, '//////7///8=');
expect(decodeIntsFromLittleEndianBase64String(encoded), isNot(data));
});
}
Loading