Add redundancy to PacketServer

This commit is contained in:
zontreck 2024-08-29 07:56:42 -07:00
parent 7bcb3a755a
commit c6b8eec4ed
4 changed files with 147 additions and 55 deletions

View file

@ -1,3 +1,3 @@
class Constants { class Constants {
static const VERSION = "1.2.082924+0707"; static const VERSION = "1.2.082924+0756";
} }

View file

@ -1,6 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:libac_dart/utils/Hashing.dart';
import '../nbt/NbtIo.dart'; import '../nbt/NbtIo.dart';
import '../nbt/Stream.dart'; import '../nbt/Stream.dart';
@ -20,26 +23,63 @@ class PacketServer {
print( print(
"New connection from ${sock.remoteAddress.address}:${sock.remotePort}"); "New connection from ${sock.remoteAddress.address}:${sock.remotePort}");
ByteLayer layer = ByteLayer();
try { try {
sock.listen((data) async { sock.listen((data) {
layer.writeBytes(data);
}, onDone: () async {
layer.resetPosition();
try { try {
CompoundTag tag = await NbtIo.readFromStream(data); List<int> dataHash = layer.readBytes(256);
StringBuilder builder = StringBuilder(); int sequenceID = layer.readLong();
Tag.writeStringifiedNamedTag(tag, builder, 0); print("Sequence ID in request: $sequenceID");
print("Request from client: \n${builder}"); int numBytes = layer.readLong();
C2SRequestPacket request = C2SRequestPacket(); List<int> remainingBytes = layer.readBytes(numBytes);
request.decodeTag(tag);
PacketResponse reply = await request.handleServerPacket(); String sha256OriginalHash = Hashing.bytes2Hash(dataHash);
String sha256Hash =
Hashing.bytes2Hash(Hashing.sha1Sum(remainingBytes));
if (sha256OriginalHash == sha256Hash) {
CompoundTag tag = await NbtIo.readFromStream(
Uint8List.fromList(remainingBytes));
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(tag, builder, 0);
// Server uses NBT to communicate print("Request from client: \n${builder}");
builder = StringBuilder();
Tag.writeStringifiedNamedTag(reply.replyDataTag, builder, 0);
print("Response to client: \n${builder}"); C2SRequestPacket request = C2SRequestPacket();
sock.add(await NbtIo.writeToStream(reply.replyDataTag)); request.decodeTag(tag);
PacketResponse reply = await request.handleServerPacket();
// Server uses NBT to communicate
builder = StringBuilder();
Tag.writeStringifiedNamedTag(reply.replyDataTag, builder, 0);
print("Response to client: \n${builder}");
Uint8List nbtData = await NbtIo.writeToStream(reply.replyDataTag);
layer.clear();
layer.writeLong(sequenceID);
layer.writeByte(0xFF); // Successful receipt
layer.writeBytes(Hashing.sha256Sum(nbtData));
layer.writeLong(nbtData.lengthInBytes);
layer.writeBytes(nbtData);
sock.add(layer.bytes);
} else {
// Return a failure packet
layer.clear();
layer.writeLong(sequenceID);
layer.writeByte(0x00);
sock.add(layer.bytes); // Failure code.
print(
"ERROR: The inbound hash did not match real hash: $sha256OriginalHash != $sha256Hash\n> REFUSING TO PROCESS PACKET. SENDING ERROR CODE TO CLIENT");
}
} catch (E, stack) { } catch (E, stack) {
response.contents response.contents
.put("error", StringTag.valueOf("Malformed request packet")); .put("error", StringTag.valueOf("Malformed request packet"));
@ -50,11 +90,11 @@ class PacketServer {
await sock.flush(); await sock.flush();
sock.close(); sock.close();
} }
}, onDone: () { layer.clear();
sock.close();
}, onError: (E) { }, onError: (E) {
print("ERROR: ${E}"); print("ERROR: ${E}");
sock.close(); sock.close();
layer.clear();
}); });
} catch (E) { } catch (E) {
sock.close(); sock.close();
@ -68,6 +108,7 @@ class PacketClient {
bool connected = false; bool connected = false;
String lastIP = ""; String lastIP = "";
int port = 25306; int port = 25306;
int packetSequence = 0;
PacketClient(); PacketClient();
@ -93,45 +134,81 @@ class PacketClient {
C2SRequestPacket request = C2SRequestPacket(); C2SRequestPacket request = C2SRequestPacket();
request.payload = packet; request.payload = packet;
request.cap = packet.getChannelID(); request.cap = packet.getChannelID();
bool success = false;
socket!.add(await NbtIo.writeToStream(request.encodeTag().asCompoundTag())); ByteLayer layer = ByteLayer();
CompoundTag ct = CompoundTag();
Completer<void> onCompletion = Completer();
socket!.listen((data) async {
try {
CompoundTag result = await NbtIo.readFromStream(data);
StringBuilder builder = StringBuilder(); Uint8List nbtData =
Tag.writeStringifiedNamedTag(result, builder, 0); await NbtIo.writeToStream(request.encodeTag().asCompoundTag());
List<int> nbtDataHash = Hashing.sha256Sum(nbtData);
print("Response from server: \n${builder}"); ByteLayer reply = ByteLayer();
ct.put("result", result); CompoundTag NBTTag = CompoundTag();
} catch (E, S) {
print(S);
} finally {
onCompletion.complete();
}
}, onError: (E, stack) {
print("ERROR: ${E}\n${stack}");
if (!onCompletion.isCompleted) onCompletion.complete();
}, onDone: () {
print("Request completed");
});
await onCompletion.future; while (!success) {
await close(); layer.clear();
layer.writeBytes(nbtDataHash);
layer.writeLong(packetSequence);
layer.writeLong(nbtData.lengthInBytes);
layer.writeBytes(nbtData);
await startConnect(lastIP, port); Completer responseWait = Completer();
S2CResponse reply = S2CResponse();
try { socket!.add(layer.bytes);
reply.decodeTag(ct.get("result")!.asCompoundTag());
} catch (E, stack) { socket!.listen((data) {
reply.contents = CompoundTag(); // This is essentially a null response reply.writeBytes(data);
reply.contents.put("error", StringTag.valueOf(E.toString())); }, onDone: () async {
reply.contents.put("stacktrace", StringTag.valueOf(stack.toString())); // Validate response validity
reply.resetPosition();
int sequence = reply.readLong();
int successReceipt = reply.readByte();
List<int> serverHash = reply.readBytes(256);
String srvHashStr = Hashing.bytes2Hash(serverHash);
int numBytes = reply.readLong();
List<int> pktBytes = reply.readBytes(numBytes);
String pktHash = Hashing.bytes2Hash(Hashing.sha256Sum(pktBytes));
if (successReceipt == 0xFF &&
packetSequence == sequence &&
srvHashStr == pktHash) success = true;
if (success) {
NBTTag = await NbtIo.readFromStream(Uint8List.fromList(pktBytes));
}
responseWait.complete();
}, onError: () {
if (!responseWait.isCompleted) responseWait.complete();
});
await responseWait.future;
packetSequence++;
if (!success) await Future.delayed(Duration(seconds: 5));
} }
return reply; CompoundTag ct = CompoundTag();
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(NBTTag, builder, 0);
print("Response from server: \n${builder}");
ct.put("result", NBTTag);
await close();
if (shouldReconnect) await startConnect(lastIP, port);
S2CResponse replyPkt = S2CResponse();
try {
replyPkt.decodeTag(ct.get("result")!.asCompoundTag());
} catch (E, stack) {
replyPkt.contents = CompoundTag(); // This is essentially a null response
replyPkt.contents.put("error", StringTag.valueOf(E.toString()));
replyPkt.contents.put("stacktrace", StringTag.valueOf(stack.toString()));
}
return replyPkt;
} }
Future<void> close() async { Future<void> close() async {

View file

@ -5,17 +5,17 @@ import 'package:crypto/crypto.dart';
class Hashing { class Hashing {
/// This will generate the md5 bytes and hash it using #bytes2Hash /// This will generate the md5 bytes and hash it using #bytes2Hash
static String md5Hash(String input) { static String md5Hash(String input) {
return bytes2Hash(md5Sum(input)); return bytes2Hash(md5SumStr(input));
} }
/// This will generate the Sha1 bytes and hash it using #bytes2Hash /// This will generate the Sha1 bytes and hash it using #bytes2Hash
static String sha1Hash(String input) { static String sha1Hash(String input) {
return bytes2Hash(sha1Sum(input)); return bytes2Hash(sha1SumStr(input));
} }
/// This will generate the Sha256 bytes and hash it using #bytes2Hash. /// This will generate the Sha256 bytes and hash it using #bytes2Hash.
static String sha256Hash(String input) { static String sha256Hash(String input) {
return bytes2Hash(sha256Sum(input)); return bytes2Hash(sha256SumStr(input));
} }
/// This function takes a list of bytes and returns a hash string /// This function takes a list of bytes and returns a hash string
@ -29,17 +29,32 @@ class Hashing {
} }
/// This function returns the bytes instead of a hash string /// This function returns the bytes instead of a hash string
static List<int> md5Sum(String input) { static List<int> md5SumStr(String input) {
return md5.convert(utf8.encode(input)).bytes; return md5.convert(utf8.encode(input)).bytes;
} }
/// This functions returns the bytes instead of a hash string /// This functions returns the bytes instead of a hash string
static List<int> sha1Sum(String input) { static List<int> sha1SumStr(String input) {
return sha1.convert(utf8.encode(input)).bytes; return sha1.convert(utf8.encode(input)).bytes;
} }
/// This function returns the bytes instead of a hash string /// This function returns the bytes instead of a hash string
static List<int> sha256Sum(String input) { static List<int> sha256SumStr(String input) {
return sha256.convert(utf8.encode(input)).bytes; return sha256.convert(utf8.encode(input)).bytes;
} }
/// This function returns the bytes instead of a hash string
static List<int> md5Sum(List<int> input) {
return md5.convert(input).bytes;
}
/// This functions returns the bytes instead of a hash string
static List<int> sha1Sum(List<int> input) {
return sha1.convert(input).bytes;
}
/// This function returns the bytes instead of a hash string
static List<int> sha256Sum(List<int> input) {
return sha256.convert(input).bytes;
}
} }

View file

@ -1,6 +1,6 @@
name: libac_dart name: libac_dart
description: "Aria's Creations code library" description: "Aria's Creations code library"
version: 1.2.082924+0707 version: 1.2.082924+0756
homepage: "https://zontreck.com" homepage: "https://zontreck.com"