start static method
- int port
Packet Data Format:
4 bytes (int) - Version
8 Bytes (Long) - Total expected bytes of packet
8 bytes (Long) - Sequence ID
1 byte - Encryption Type
8 bytes (Long) - Number of bytes to read
Response Format:
4 Bytes (int) - Version
8 bytes (Long) - Total expected bytes in packet
1 byte - Success flag, Zero or 255 currently.
1 byte - Encryption Type
8 byes (Long) - Packet Length
Implementation
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) async {
layer.writeBytes(data);
var oldPos = layer.currentPosition;
layer.resetPosition();
int version = layer.readInt();
int pktTotalExpected = layer.readLong();
if (pktTotalExpected <= layer.length) {
// Allow Processing
} else {
layer.restorePosition(oldPos);
return;
}
layer.resetPosition();
layer.readInt();
layer.readLong(); // This is unused outside of the above sanity check.
try {
int encryptType = layer.readByte();
EncryptionType ENCType = EncryptionType.valueOf(encryptType);
int sequenceID = layer.readLong();
print("Sequence ID in request: $sequenceID");
int numBytes = layer.readLong();
List<int> remainingBytes = layer.readBytes(numBytes);
if (ENCType == EncryptionType.AES) {
int ivLen = layer.readInt();
List<int> ivBytes = layer.readBytes(ivLen);
AESData encData = AESData(iv: ivBytes, data: remainingBytes);
AESCipher aes = await AESCipher.useKey(AESKey);
remainingBytes = await aes.decrypt(encData);
} else if (ENCType == EncryptionType.XTEA) {
XTEA xtea = await XTEA(PSK);
remainingBytes =
xtea.decipher(Uint8List.fromList(remainingBytes));
}
CompoundTag tag =
await NbtIo.readFromStream(Uint8List.fromList(remainingBytes))
as CompoundTag;
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.writeByte(ENCType.value);
// Encryption Subroutine
if (ENCType == EncryptionType.AES) {
AESCipher aes = AESCipher.useKey(AESKey);
var encData = await aes.encrypt(nbtData);
nbtData = Uint8List.fromList(encData.data);
layer.writeInt(encData.iv.length);
layer.writeBytes(encData.iv);
} else if (ENCType == EncryptionType.XTEA) {
XTEA tea = await XTEA(PSK);
nbtData = Uint8List.fromList(tea.encipher(nbtData));
}
layer.writeLong(nbtData.lengthInBytes);
layer.writeBytes(nbtData);
nbtData = layer.bytes;
// NOTE: Added a length indicator because SocketServer is apparently... really really dumb in its impl, and has no way to know when all data has been received, so no special event. We just have to check for it based on this initial value.
layer.clear();
layer.writeInt(PacketsConsts.VERSION);
layer.writeLong(nbtData.lengthInBytes + layer.currentPosition + 8);
layer.writeBytes(nbtData);
sock.add(layer.bytes);
} 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();
}, onDone: () {
layer.clear();
}, onError: (E) {
print("ERROR: ${E}");
sock.close();
layer.clear();
});
} catch (E) {
sock.close();
}
}
}