LibAC-dart/lib/packets/packets.dart

532 lines
13 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:libac_dart/utils/Hashing.dart';
import '../nbt/NbtIo.dart';
import '../nbt/Stream.dart';
import '../nbt/Tag.dart';
import '../nbt/impl/CompoundTag.dart';
import '../nbt/impl/StringTag.dart';
class PacketServer {
static ServerSocket? socket;
static bool shouldRestart = true;
static Future<void> start(int port) async {
socket = await ServerSocket.bind(InternetAddress.anyIPv4, port);
print("Server now listening on port ${port}");
await for (var sock in socket!) {
S2CResponse response = S2CResponse();
print(
"New connection from ${sock.remoteAddress.address}:${sock.remotePort}");
ByteLayer layer = ByteLayer();
try {
sock.listen((data) {
layer.writeBytes(data);
}, onDone: () async {
layer.resetPosition();
try {
List<int> dataHash = layer.readBytes(256);
int sequenceID = layer.readLong();
print("Sequence ID in request: $sequenceID");
int numBytes = layer.readLong();
List<int> remainingBytes = layer.readBytes(numBytes);
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);
print("Request from client: \n${builder}");
C2SRequestPacket request = C2SRequestPacket();
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) {
response.contents
.put("error", StringTag.valueOf("Malformed request packet"));
print(
"Something went wrong. Malformed request? \n\n${E}\n\n${stack}\n\n\n\n");
} finally {
await sock.flush();
sock.close();
}
layer.clear();
}, onError: (E) {
print("ERROR: ${E}");
sock.close();
layer.clear();
});
} catch (E) {
sock.close();
}
}
}
}
class PacketClient {
Socket? socket;
bool connected = false;
String lastIP = "";
int port = 25306;
int packetSequence = 0;
PacketClient();
Future<void> startConnect(String IPAddress, int port) async {
try {
socket = await Socket.connect(IPAddress, port);
connected = true;
lastIP = IPAddress;
this.port = port;
} catch (E) {
connected = false;
socket = null;
}
}
/// Tries to send a packet to the connected server
///
/// On success, returns either, the decoded [S2CResponse], or on error a S2CResponse containing an error and a stacktrace as [StringTag]
Future<S2CResponse> send(IPacket packet, bool shouldReconnect) async {
if (!connected) {
return S2CResponse();
}
C2SRequestPacket request = C2SRequestPacket();
request.payload = packet;
request.cap = packet.getChannelID();
bool success = false;
ByteLayer layer = ByteLayer();
Uint8List nbtData =
await NbtIo.writeToStream(request.encodeTag().asCompoundTag());
List<int> nbtDataHash = Hashing.sha256Sum(nbtData);
ByteLayer reply = ByteLayer();
CompoundTag NBTTag = CompoundTag();
while (!success) {
layer.clear();
layer.writeBytes(nbtDataHash);
layer.writeLong(packetSequence);
layer.writeLong(nbtData.lengthInBytes);
layer.writeBytes(nbtData);
Completer responseWait = Completer();
socket!.add(layer.bytes);
socket!.listen((data) {
reply.writeBytes(data);
}, onDone: () async {
// 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));
}
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 {
await socket!.close();
connected = false;
}
}
abstract class IPacket with NbtEncodable, JsonEncodable {
String getChannelID();
// This function handles the packet
Future<PacketResponse> handleServerPacket();
Future<void> handleClientPacket();
NetworkDirection direction();
}
class StopServerPacket extends IPacket {
@override
void decodeJson(String params) {}
@override
void decodeTag(Tag tag) {}
@override
NetworkDirection direction() {
return NetworkDirection.ClientToServer;
}
@override
String encodeJson() {
return json.encode({});
}
@override
Tag encodeTag() {
return CompoundTag();
}
@override
void fromJson(Map<String, dynamic> js) {}
@override
String getChannelID() {
return "StopServer";
}
@override
Future<PacketResponse> handleServerPacket() async {
// We're now on the server. Handle the packet with a response to the client
PacketServer.shouldRestart = false;
S2CResponse response = S2CResponse();
return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
}
@override
Map<String, dynamic> toJson() {
return {};
}
@override
Future<void> handleClientPacket() {
throw UnimplementedError();
}
}
class PacketResponse {
static final nil = PacketResponse(replyDataTag: CompoundTag());
PacketResponse({required this.replyDataTag});
CompoundTag replyDataTag = CompoundTag();
}
class PacketRegistry {
Map<String, IPacket Function()> _registry = {};
static PacketRegistry _inst = PacketRegistry._();
PacketRegistry._() {
registerDefaults();
}
factory PacketRegistry() {
return _inst;
}
int get count => _registry.length;
void register(IPacket packet, IPacket Function() packetResolver) {
_registry[packet.getChannelID()] = packetResolver;
}
IPacket getPacket(String channel) {
if (_registry.containsKey(channel)) {
IPacket Function() callback = _registry[channel]!;
return callback();
} else
throw Exception("No such channel has been registered");
}
void registerDefaults() {
register(S2CResponse(), () {
return S2CResponse();
});
register(C2SRequestPacket(), () {
return C2SRequestPacket();
});
register(StopServerPacket(), () {
return StopServerPacket();
});
register(C2SPing(), () {
return C2SPing();
});
}
}
enum NetworkDirection { ClientToServer, ServerToClient }
enum PacketOperation { Encode, Decode }
class S2CResponse implements IPacket {
CompoundTag contents = CompoundTag();
@override
NetworkDirection direction() {
return NetworkDirection.ServerToClient;
}
@override
String getChannelID() {
return "Response";
}
@override
Future<PacketResponse> handleServerPacket() async {
// We can't predict handling for this type, it is a data packet response with no pre-defined structure.
return PacketResponse.nil;
}
@override
void decodeJson(String encoded) {
fromJson(json.decode(encoded));
}
@override
void decodeTag(Tag encoded) {
CompoundTag ct = encoded as CompoundTag;
contents = ct.get("contents")!.asCompoundTag();
}
@override
String encodeJson() {
return json.encode(toJson());
}
@override
Tag encodeTag() {
CompoundTag tag = CompoundTag();
tag.put("contents", contents);
return tag;
}
@override
void fromJson(Map<String, dynamic> params) {}
@override
Map<String, dynamic> toJson() {
return {}; // Operation is not supported at this time.
}
@override
Future<void> handleClientPacket() async {
// We haven't got anything to process. This is structured data
}
}
class C2SRequestPacket implements IPacket {
String cap = ""; // Packet channel
late IPacket payload;
@override
void decodeJson(String encoded) {
fromJson(json.decode(encoded));
}
@override
void decodeTag(Tag encoded) {
CompoundTag tag = encoded.asCompoundTag();
String cap = tag.get("cap")!.asString();
payload = PacketRegistry().getPacket(cap);
payload.decodeTag(tag.get("payload")!.asCompoundTag());
}
@override
NetworkDirection direction() {
return NetworkDirection.ClientToServer;
}
@override
String encodeJson() {
return json.encode(toJson());
}
@override
Tag encodeTag() {
CompoundTag tag = CompoundTag();
tag.put("cap", StringTag.valueOf(payload.getChannelID()));
tag.put("payload", payload.encodeTag());
return tag;
}
@override
void fromJson(Map<String, dynamic> params) {
String cap = params['cap'] as String;
payload = PacketRegistry().getPacket(cap);
payload.fromJson(params['payload']);
}
@override
String getChannelID() {
return "C2SRequest";
}
@override
Future<PacketResponse> handleServerPacket() async {
// This has no internal handling
return payload.handleServerPacket();
}
@override
Map<String, dynamic> toJson() {
return {"cap": payload.getChannelID(), "payload": payload.toJson()};
}
@override
Future<void> handleClientPacket() {
throw UnimplementedError();
}
}
class C2SPing implements IPacket {
String clientVersion = "";
@override
void decodeJson(String params) {
fromJson(json.decode(params));
}
@override
void decodeTag(Tag tag) {
clientVersion = tag.asCompoundTag().get("version")!.asString();
}
@override
NetworkDirection direction() {
return NetworkDirection.ClientToServer;
}
@override
String encodeJson() {
return json.encode(toJson());
}
@override
Tag encodeTag() {
CompoundTag tag = CompoundTag();
tag.put("version", StringTag.valueOf(clientVersion));
return tag;
}
@override
void fromJson(Map<String, dynamic> js) {
clientVersion = js['version'] as String;
}
@override
String getChannelID() {
return "Ping";
}
@override
Future<PacketResponse> handleServerPacket() async {
CompoundTag tag = CompoundTag();
tag.put("pong", StringTag.valueOf(Platform.version));
S2CResponse response = S2CResponse();
response.contents = tag;
PacketResponse reply =
PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
return reply;
}
@override
Map<String, dynamic> toJson() {
return {"version": clientVersion};
}
@override
Future<void> handleClientPacket() {
throw UnimplementedError();
}
}
mixin JsonEncodable {
String encodeJson();
void decodeJson(String params);
Map<String, dynamic> toJson();
void fromJson(Map<String, dynamic> js);
}
mixin NbtEncodable {
Tag encodeTag();
void decodeTag(Tag tag);
}