Finish testing and assembling a basic server and client structure

This commit is contained in:
zontreck 2024-05-22 20:10:41 -07:00
parent 5c4a8c6a58
commit 3be0a9ab5f
5 changed files with 192 additions and 38 deletions

24
bin/client_test.dart Normal file
View file

@ -0,0 +1,24 @@
import 'package:libac_flutter/nbt/Stream.dart';
import 'package:libac_flutter/nbt/Tag.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/packets/packets.dart';
void main() async {
PacketRegistry reg = PacketRegistry();
reg.registerDefaults();
PacketClient client = PacketClient();
await client.startConnect("127.0.0.1");
S2CResponse response = await client.send(C2SPing());
CompoundTag tag = response.contents;
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(tag, builder, 0);
print("Response from server: \n${builder}");
await client.send(StopServerPacket());
if (client.connected) await client.close();
return;
}

5
bin/server_test.dart Normal file
View file

@ -0,0 +1,5 @@
import 'package:libac_flutter/packets/packets.dart';
void main() async {
await PacketServer.start();
}

7
compile.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/bash
mkdir out
rm -rf out/*
flutter pub publish -f --skip-validation
dart compile exe -o out/server_test bin/server_test.dart
dart compile exe -o out/client_test bin/client_test.dart

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
@ -6,8 +7,11 @@ import 'package:libac_flutter/nbt/Tag.dart';
import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
import 'package:libac_flutter/nbt/impl/StringTag.dart'; import 'package:libac_flutter/nbt/impl/StringTag.dart';
import '../nbt/Stream.dart';
class PacketServer { class PacketServer {
static ServerSocket? socket; static ServerSocket? socket;
static bool shouldRestart = true;
static Future<void> start() async { static Future<void> start() async {
socket = await ServerSocket.bind(InternetAddress.anyIPv4, 25306); socket = await ServerSocket.bind(InternetAddress.anyIPv4, 25306);
print("Server now listening on port 25306"); print("Server now listening on port 25306");
@ -18,24 +22,38 @@ class PacketServer {
try { try {
sock.listen((data) async { sock.listen((data) async {
S2CStatusResponse response = S2CStatusResponse(); S2CResponse response = S2CResponse();
try { try {
CompoundTag tag = await NbtIo.readFromStream(data); CompoundTag tag = await NbtIo.readFromStream(data);
StringBuilder builder = StringBuilder();
Tag.writeStringifiedNamedTag(tag, builder, 0);
print("Request from client: \n${builder}");
C2SRequestPacket request = C2SRequestPacket(); C2SRequestPacket request = C2SRequestPacket();
request.decodeTag(tag); request.decodeTag(tag);
PacketResponse reply = await request.handlePacket(); PacketResponse reply = await request.handleServerPacket();
// Server uses NBT to communicate // Server uses NBT to communicate
sock.add(await NbtIo.writeToStream(reply.replyDataTag)); sock.add(await NbtIo.writeToStream(reply.replyDataTag));
} catch (E, stack) { } catch (E, stack) {
response.reason = "Malformed request packet"; response.contents
.put("error", StringTag.valueOf("Malformed request packet"));
sock.add( sock.add(
await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); await NbtIo.writeToStream(response.encodeTag() as CompoundTag));
} finally {
await sock.flush();
sock.close();
if (!shouldRestart) {
await socket!.close();
}
} }
}, onDone: () { }, onDone: () {
sock.close(); sock.close();
}, onError: () { }, onError: (E) {
print("ERROR: ${E}");
sock.close(); sock.close();
}); });
} catch (E) { } catch (E) {
@ -48,36 +66,53 @@ class PacketServer {
class PacketClient { class PacketClient {
Socket? socket; Socket? socket;
bool connected = false; bool connected = false;
String lastIP = "";
PacketClient(String IPAddress); PacketClient();
Future<void> startConnect(String IPAddress) async { Future<void> startConnect(String IPAddress) async {
try { try {
socket = await Socket.connect(IPAddress, 25306); socket = await Socket.connect(IPAddress, 25306);
connected = true; connected = true;
lastIP = IPAddress;
} catch (E, stack) { } catch (E, stack) {
connected = false; connected = false;
socket = null; socket = null;
print(stack);
} }
} }
Future<CompoundTag> send(IPacket packet) async { Future<S2CResponse> send(IPacket packet) async {
if (!connected) { if (!connected) {
return CompoundTag(); return S2CResponse();
} }
socket!.add(await NbtIo.writeToStream(packet.encodeTag().asCompoundTag())); C2SRequestPacket request = C2SRequestPacket();
request.payload = packet;
request.cap = packet.getChannelID();
socket!.add(await NbtIo.writeToStream(request.encodeTag().asCompoundTag()));
CompoundTag ct = CompoundTag(); CompoundTag ct = CompoundTag();
Completer<void> onCompletion = Completer();
socket!.listen((data) async { socket!.listen((data) async {
ct = await NbtIo.readFromStream(data); CompoundTag result = await NbtIo.readFromStream(data);
ct.put("result", result);
}, onError: (E) {
print("ERROR: ${E}");
}, onDone: () {
print("Request completed");
onCompletion.complete();
}); });
return ct; await onCompletion.future;
await close();
await startConnect(lastIP);
S2CResponse reply = S2CResponse();
reply.decodeTag(ct.get("result")!.asCompoundTag());
return reply;
} }
void close() { Future<void> close() async {
socket!.close(); await socket!.close();
connected = false; connected = false;
} }
} }
@ -86,7 +121,8 @@ abstract class IPacket with NbtEncodable, JsonEncodable {
String getChannelID(); String getChannelID();
// This function handles the packet // This function handles the packet
Future<PacketResponse> handlePacket(); Future<PacketResponse> handleServerPacket();
Future<void> handleClientPacket();
NetworkDirection direction(); NetworkDirection direction();
} }
@ -121,16 +157,24 @@ class StopServerPacket extends IPacket {
} }
@override @override
Future<PacketResponse> handlePacket() async { Future<PacketResponse> handleServerPacket() async {
PacketServer.socket!.close(); // We're now on the server. Handle the packet with a response to the client
PacketServer.shouldRestart = false;
return PacketResponse(replyDataTag: CompoundTag()); S2CResponse response = S2CResponse();
return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag());
} }
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return {}; return {};
} }
@override
Future<void> handleClientPacket() {
throw UnimplementedError();
}
} }
class PacketResponse { class PacketResponse {
@ -145,7 +189,9 @@ class PacketRegistry {
Map<String, IPacket Function()> _registry = {}; Map<String, IPacket Function()> _registry = {};
static PacketRegistry _inst = PacketRegistry._(); static PacketRegistry _inst = PacketRegistry._();
PacketRegistry._(); PacketRegistry._() {
registerDefaults();
}
factory PacketRegistry() { factory PacketRegistry() {
return _inst; return _inst;
@ -166,8 +212,8 @@ class PacketRegistry {
} }
void registerDefaults() { void registerDefaults() {
register(S2CStatusResponse(), () { register(S2CResponse(), () {
return S2CStatusResponse(); return S2CResponse();
}); });
register(C2SRequestPacket(), () { register(C2SRequestPacket(), () {
return C2SRequestPacket(); return C2SRequestPacket();
@ -175,6 +221,9 @@ class PacketRegistry {
register(StopServerPacket(), () { register(StopServerPacket(), () {
return StopServerPacket(); return StopServerPacket();
}); });
register(C2SPing(), () {
return C2SPing();
});
} }
} }
@ -182,8 +231,8 @@ enum NetworkDirection { ClientToServer, ServerToClient }
enum PacketOperation { Encode, Decode } enum PacketOperation { Encode, Decode }
class S2CStatusResponse implements IPacket { class S2CResponse implements IPacket {
String reason = ""; CompoundTag contents = CompoundTag();
@override @override
NetworkDirection direction() { NetworkDirection direction() {
@ -192,12 +241,12 @@ class S2CStatusResponse implements IPacket {
@override @override
String getChannelID() { String getChannelID() {
return "StatusResponse"; return "Response";
} }
@override @override
Future<PacketResponse> handlePacket() async { Future<PacketResponse> handleServerPacket() async {
// No handling is required for this packet type // We can't predict handling for this type, it is a data packet response with no pre-defined structure.
return PacketResponse.nil; return PacketResponse.nil;
} }
@ -209,7 +258,7 @@ class S2CStatusResponse implements IPacket {
@override @override
void decodeTag(Tag encoded) { void decodeTag(Tag encoded) {
CompoundTag ct = encoded as CompoundTag; CompoundTag ct = encoded as CompoundTag;
reason = ct.get("reason")!.asString(); contents = ct.get("contents")!.asCompoundTag();
} }
@override @override
@ -220,23 +269,22 @@ class S2CStatusResponse implements IPacket {
@override @override
Tag encodeTag() { Tag encodeTag() {
CompoundTag tag = CompoundTag(); CompoundTag tag = CompoundTag();
tag.put("reason", StringTag.valueOf(reason)); tag.put("contents", contents);
return tag; return tag;
} }
@override @override
void fromJson(Map<String, dynamic> params) { void fromJson(Map<String, dynamic> params) {}
reason = params["reason"] as String;
}
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
Map<String, dynamic> map = { return {}; // Operation is not supported at this time.
"reason": reason, }
};
return map; @override
Future<void> handleClientPacket() async {
// We haven't got anything to process. This is structured data
} }
} }
@ -289,15 +337,85 @@ class C2SRequestPacket implements IPacket {
} }
@override @override
Future<PacketResponse> handlePacket() async { Future<PacketResponse> handleServerPacket() async {
// This has no internal handling // This has no internal handling
return payload.handlePacket(); return payload.handleServerPacket();
} }
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return {"cap": payload.getChannelID(), "payload": payload.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 { mixin JsonEncodable {

View file

@ -1,6 +1,6 @@
name: libac_flutter name: libac_flutter
description: "Aria's Creations code library" description: "Aria's Creations code library"
version: 1.0.11 version: 1.0.12
homepage: "https://zontreck.com" homepage: "https://zontreck.com"
environment: environment: