start static method

Future<void> start(
  1. 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 - NBT Data / Encrypted NBT Data

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

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