send method

Future<S2CResponse> send(
  1. IPacket packet,
  2. bool shouldReconnect
)

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 - NBT Data

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 - NBT Data

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;
}