Push more utilities and UUID stuff

This commit is contained in:
zontreck 2024-05-06 20:49:48 -07:00
parent dd6ee1bf60
commit 9b7b84153f
7 changed files with 325 additions and 0 deletions

View file

@ -1,11 +1,13 @@
import 'package:libac_flutter/nbt/impl/ByteTag.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/DoubleTag.dart';
import 'package:libac_flutter/nbt/impl/IntArrayTag.dart';
import 'package:libac_flutter/nbt/impl/IntTag.dart';
import 'package:libac_flutter/nbt/impl/ListTag.dart';
import 'package:libac_flutter/utils/Vector3.dart';
import '../utils/Vector2.dart';
import '../utils/uuid/UUID.dart';
class NbtUtils {
static void writeBoolean(CompoundTag tag, String name, bool b) {
@ -89,4 +91,29 @@ class NbtUtils {
return Vector3i.ZERO;
}
}
static List<int> _msbLsbToIntArray(int msb, int lsb) {
return [msb >> 32, msb, lsb >> 32, lsb];
}
static List<int> _uuidToIntArray(UUID ID) {
return _msbLsbToIntArray(
ID.getMostSignificantBits(), ID.getLeastSignificantBits());
}
static UUID _uuidFromIntArray(List<int> values) {
return UUID((values[0] << 32 | values[1] & 4294967295),
(values[2] << 32 | values[3] & 4294967295));
}
static void writeUUID(CompoundTag tag, String name, UUID ID) {
tag.put(name, IntArrayTag.valueOf(_uuidToIntArray(ID)));
}
static UUID readUUID(CompoundTag tag, String name) {
if (!tag.contains(name))
return UUID.ZERO;
else
return _uuidFromIntArray(tag.get(name)!.asIntArray());
}
}

View file

@ -283,4 +283,74 @@ class ByteLayer {
int value = readLong();
return (value >> 1) ^ (-(value & 1));
}
void writeBytes(Iterable<int> bytes) {
for (int byte in bytes) {
writeByte(byte);
}
}
List<int> readBytes(int num) {
List<int> lst = [];
for (int i = 0; i < num; i++) {
lst.add(readByte());
}
return lst;
}
void setBit(int position, int maskToSet) {
if (position < _byteBuffer.length) {
// Set the value now
seek(position);
int current = readByte();
seek(position);
current |= maskToSet;
writeByte(current);
}
}
void clearBit(int position, int maskToClear) {
if (position < _byteBuffer.length) {
// Lets clear the bit
seek(position);
int current = readByte();
current &= maskToClear;
seek(position);
writeByte(current);
}
}
bool checkBit(int position, int mask) {
if (position < _byteBuffer.length) {
seek(position);
int current = readByte();
return (current & mask) == mask;
} else
return false;
}
int getBit(int position) {
if (position < _byteBuffer.length) {
seek(position);
return readByte();
} else
return 0;
}
void seek(int position) {
_position = 0;
_ensureCapacity(position);
_position = position;
}
void waitSetBit(int position, int maskToClear, int maskToSet) {
clearBit(position, maskToClear);
setBit(position, maskToSet);
while (!checkBit(position, maskToSet)) {
clearBit(position, maskToClear);
setBit(position, maskToSet);
}
}
}

View file

@ -9,6 +9,10 @@ class IntArrayTag extends Tag {
this.value.addAll(value);
}
static IntArrayTag valueOf(List<int> value) {
return IntArrayTag._(value);
}
@override
void readValue(ByteLayer data) {
int count = data.readInt();

161
lib/utils/uuid/UUID.dart Normal file
View file

@ -0,0 +1,161 @@
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:libac_flutter/nbt/Stream.dart';
class UUID {
late final int LSB;
late final int MSB;
UUID(int msb, int lsb) {
if (msb < 0)
MSB = -msb;
else
MSB = msb;
if (lsb < 0)
LSB = -lsb;
else
LSB = lsb;
}
static final UUID ZERO = UUID.generate(0);
/// Validates whether the given [uuid] is a valid UUID.
static bool validate(String uuid) {
try {
parse(uuid);
return true;
} catch (e) {
return false;
}
}
/// Parses the given [uuid] string and returns a UUID object.
static UUID parse(String uuid) {
if (validate(uuid)) {
final segments = uuid.split('-');
if (segments.length != 5) {
throw const FormatException('Invalid UUID format');
}
final msbString = segments.sublist(0, 3).join('');
final lsbString = segments.sublist(3, 5).join('');
final msb = int.parse(msbString, radix: 16);
final lsb = int.parse(lsbString, radix: 16);
return UUID(msb, lsb);
} else
return UUID.ZERO;
}
@override
String toString() {
final msbHex = MSB.toRadixString(16).padLeft(16, '0');
final lsbHex = LSB.toRadixString(16).padLeft(16, '0');
return '${msbHex.substring(0, 8)}-${msbHex.substring(8, 12)}-${msbHex.substring(12, 16)}-${lsbHex.substring(0, 4)}-${lsbHex.substring(4, 16)}';
}
/// Returns the Most Significant Bits (MSB) long value of the UUID.
int getMostSignificantBits() => MSB;
/// Returns the Least Significant Bits (LSB) long value of the UUID.
int getLeastSignificantBits() => LSB;
/// Factory method to generate UUID of the specific version.
factory UUID.generate(int version, {List<Object>? parameters}) {
List<Object> params = [];
if (parameters == null) {
if (version != 4) {
return UUID.generate(4);
}
} else
params = parameters!;
switch (version) {
case 0:
return UUID(0, 0);
case 3:
{
if (params.length != 2)
throw Exception(
"UUID v3 requires two parameters, [namespace,name]");
String namespace = params[0] as String;
String name = params[1] as String;
ByteLayer layer = ByteLayer();
if (!namespace.isEmpty) {
final namespaceBytes = utf8.encode(namespace);
layer.writeBytes(namespaceBytes);
}
if (!name.isEmpty) {
final nameBytes = utf8.encode(name);
layer.writeBytes(nameBytes);
}
var bytes = md5.convert(List.from(layer.bytes)).bytes;
layer.clear();
layer.writeBytes(bytes);
layer.waitSetBit(6, 0x0F, 0x30);
layer.waitSetBit(8, 0x3F, 0x80);
layer.resetPosition();
return UUID(layer.readLong(), layer.readLong());
}
case 4:
{
ByteLayer layer = ByteLayer();
final random = Random.secure();
layer.writeLong(
(random.nextInt(0xFFFFFFFF) << 32) | random.nextInt(0xFFFFFFFF));
layer.writeLong(
(random.nextInt(0xFFFFFFFF) << 32) | random.nextInt(0xFFFFFFFF));
layer.waitSetBit(6, 0x0F, 0x40);
layer.waitSetBit(8, 0x3F, 0x80);
layer.resetPosition();
return UUID(layer.readLong(), layer.readLong());
}
case 5:
{
ByteLayer layer = ByteLayer();
if (params.length != 2)
throw Exception(
"UUID v5 requires two parameters, [namespace,name]");
String namespace = params[0] as String;
String name = params[1] as String;
if (!namespace.isEmpty) {
final namespaceBytes = utf8.encode(namespace);
layer.writeBytes(namespaceBytes);
}
if (!name.isEmpty) {
final nameBytes = utf8.encode(name);
layer.writeBytes(nameBytes);
}
final hashBytes = sha1.convert(List.from(layer.bytes)).bytes;
layer.clear();
layer.writeBytes(hashBytes);
layer.waitSetBit(6, 0x0F, 0x50);
layer.waitSetBit(8, 0x3F, 0x80);
layer.resetPosition();
return UUID(layer.readLong(), layer.readLong());
}
default:
throw ArgumentError('Unsupported UUID version: $version');
}
}
}

View file

@ -8,6 +8,7 @@ environment:
flutter: ">=1.17.0"
dependencies:
crypto: ^3.0.3
flutter:
sdk: flutter

View file

@ -2,8 +2,10 @@ import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:libac_flutter/nbt/NbtIo.dart';
import 'package:libac_flutter/nbt/NbtUtils.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/StringTag.dart';
import 'package:libac_flutter/utils/uuid/UUID.dart';
void main() {
test('read non-compressed helloworld NBT', () async {
@ -44,4 +46,13 @@ void main() {
expect(tag.get("doubleTest")!.asDouble(), 0.4931287132182315);
expect(tag.get("floatTest")!.asFloat(), 0.49823147);
});
test("Generate a UUID v4, save to NBT, and read it back again", () async {
var id = UUID.generate(4);
CompoundTag tag = CompoundTag();
NbtUtils.writeUUID(tag, "test", id);
var newID = NbtUtils.readUUID(tag, "test");
expect(id.toString(), newID.toString());
});
}

51
test/uuid_test.dart Normal file
View file

@ -0,0 +1,51 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/utils/uuid/UUID.dart';
void main() {
test("Generate a UUID v4", () {
var ID = UUID.generate(4);
expect(UUID.validate(ID.toString()), true);
});
test("Generate 10 UUIDv4", () {
List<UUID> ID = [];
for (int i = 0; i < 10; i++) {
ID.add(UUID.generate(4));
}
for (UUID sID in ID) {
print("ID : ${sID}}");
}
});
test("Check UUIDv4 for validity", () {
var ID = UUID.generate(4);
ByteLayer layer = ByteLayer();
layer.writeLong(ID.getMostSignificantBits());
layer.writeLong(ID.getLeastSignificantBits());
print(
"Checking version bit: ${layer.checkBit(6, 0x40)} - ${layer.getBit(6)}");
expect(layer.checkBit(6, 0x40), true);
});
test("Generate and check a UUIDv3", () {
var ID3 = UUID.generate(3, parameters: ["Test", "Test2"]);
ByteLayer layer = ByteLayer();
layer.writeLong(ID3.getMostSignificantBits());
layer.writeLong(ID3.getLeastSignificantBits());
print(
"Checking version bit: ${layer.checkBit(6, 0x30)} - ${layer.getBit(6)}");
expect(layer.checkBit(6, 0x30), true);
});
test("Test v3 implementation", () {
var expected =
"3e1b8c8a-efab-381b-ab57-4764c45b0889"; // Minecraft offline UUID : zontreck
var ID3 = UUID.generate(3, parameters: ["OfflinePlayer:zontreck", ""]);
expect(ID3.toString(), expected);
});
}