send method
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
Packet Data Format:
8 Bytes (Long) - Total expected bytes of packet minus the 8 bytes here.
8 bytes (Long) - Sequence ID
8 bytes (Long) - Number of bytes to read
Response Format:
8 bytes (Long) - Total expected bytes in packet minus the 8 bytes here.
1 byte - Success flag, Zero or 255 currently.
8 byes (Long) - Packet Length
Implementation
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());
ByteLayer reply = ByteLayer();
CompoundTag NBTTag = CompoundTag();
while (!success) {
layer.clear();
layer.writeInt(PacketsConsts.VERSION);
layer.writeLong(packetSequence);
layer.writeByte(encryptionType.value);
if (encryptionType == EncryptionType.AES) {
AESCipher aes = AESCipher.useKey(aesKey);
AESData encData = await aes.encrypt(nbtData);
layer.writeInt(encData.iv.length);
layer.writeBytes(encData.iv);
nbtData = Uint8List.fromList(encData.data);
} else if (encryptionType == EncryptionType.XTEA) {
XTEA xtea = XTEA(PSK);
nbtData = Uint8List.fromList(await xtea.encipher(nbtData.toList()));
}
layer.writeLong(nbtData.lengthInBytes);
layer.writeBytes(nbtData);
var tmpBytes = layer.bytes;
layer.clear();
layer.writeLong(tmpBytes.lengthInBytes);
layer.writeBytes(tmpBytes);
Completer responseWait = Completer();
socket!.add(layer.bytes);
socket!.listen((data) async {
reply.writeBytes(data);
var oldPos = reply.currentPosition;
reply.resetPosition();
int lenOfReply = reply.readLong();
if (lenOfReply + 8 <= reply.length) {
// We can now process the data
} else {
reply.restorePosition(oldPos);
return;
}
// Validate response validity
reply.resetPosition();
reply.readLong(); // This is unused outside of the sanity check above.
int sequence = reply.readLong();
int successReceipt = reply.readByte();
EncryptionType encType = EncryptionType.valueOf(layer.readByte());
List<int> encIV = [];
if (encType == EncryptionType.AES) {
int ivLen = layer.readInt();
encIV = layer.readBytes(ivLen);
}
int numBytes = reply.readLong();
List<int> pktBytes = reply.readBytes(numBytes);
if (successReceipt == 0xFF && packetSequence == sequence)
success = true;
if (success) {
if (encType == EncryptionType.AES) {
AESCipher aes = AESCipher.useKey(aesKey);
AESData encData = AESData(iv: encIV, data: pktBytes);
pktBytes = (await aes.decrypt(encData)).toList();
} else if (encType == EncryptionType.XTEA) {
XTEA xtea = XTEA(PSK);
pktBytes = await xtea.decipher(pktBytes);
}
NBTTag = await NbtIo.readFromStream(Uint8List.fromList(pktBytes))
as CompoundTag;
}
responseWait.complete();
}, onError: (err) {
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;
}