git » crow.game » commit a722e83

codec.api: refactor out bytebuf from read size, etc

author b0in
2023-12-07 01:26:28 UTC
committer b0in
2023-12-07 01:27:36 UTC
parent a7c072e4906549d562056f5e553c8fb0f2b186cb

codec.api: refactor out bytebuf from read size, etc

 * Introduce PacketReader which abstracts out the methods from
   bytebuf. codec will eventually be fully netty agnostic
 * code cleanup and javadoc cleanup
 * inetaddressandport simplifications
 * error logging

crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java +8 -4
crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java +2 -4
crow.game.codec.api/src/main/java/crow/game/codec/PacketReader.java +58 -0
crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java +8 -0
crow.game.codec.api/src/main/java/crow/game/codec/package-info.java +7 -11
crow.game.codec.netty/src/main/java/crow/game/codec/netty/ByteBufPacketReader.java +66 -0
crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java +11 -4
crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java +11 -1
crow.game.codec.netty/src/main/java/crow/game/codec/netty/package-info.java +4 -1
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java +2 -1
crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java +2 -1

diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java b/crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java
index 6392dfe..f5c445d 100644
--- a/crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/InetAddressAndPort.java
@@ -1,6 +1,10 @@
 package crow.game.codec;
 
-import java.net.InetAddress;
-
-/** Utility object for pairing the ip address and port together. */
-public record InetAddressAndPort(InetAddress addr, int port) {}
+/**
+ * Utility object for pairing the ip address and port together.
+ *
+ * <p>NOTE: We don't particularly care about the address itself we just need to store it so we can
+ * send it to clients, so we don't bother storing a parsed address, just the byte array that is read
+ * from the packet.
+ */
+public record InetAddressAndPort(byte[] addr, int port) {}
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java b/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java
index b23e2f0..9a33166 100644
--- a/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/PacketHandler.java
@@ -1,6 +1,5 @@
 package crow.game.codec;
 
-import java.net.InetAddress;
 import java.net.UnknownHostException;
 
 import io.netty.buffer.ByteBuf;
@@ -20,7 +19,7 @@ public interface PacketHandler<T> {
    * @return The POJO object
    * @throws Exception
    */
-  T fromPacket(Context ctx, ByteBuf buffer) throws Exception;
+  T fromPacket(Context ctx, PacketReader reader) throws Exception;
 
   /**
    * Writes the object of type T to the byte buffer.
@@ -51,9 +50,8 @@ public interface PacketHandler<T> {
     byte[] addrBuf = new byte[4];
     buf.readBytes(addrBuf);
 
-    InetAddress address = InetAddress.getByAddress(addrBuf);
     int port = buf.readShort();
-    return new InetAddressAndPort(address, port);
+    return new InetAddressAndPort(addrBuf, port);
   }
 
   /**
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/PacketReader.java b/crow.game.codec.api/src/main/java/crow/game/codec/PacketReader.java
new file mode 100644
index 0000000..49a401d
--- /dev/null
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/PacketReader.java
@@ -0,0 +1,58 @@
+package crow.game.codec;
+
+import java.net.UnknownHostException;
+
+/**
+ * Interface for decoding packets into data.
+ *
+ * <p>used to abstract out the implementation details of netty.
+ */
+public interface PacketReader {
+  /**
+   * Reads the next N bytes as a string.
+   *
+   * <p>NOTE:Strings in KRO are 0-terminated and use pre-determined lengths. So "hello" of length 20
+   * will be "hello" 0x0 0x0 0x0...
+   *
+   * @param length the size of the string, with zero padding.
+   * @return The string object.
+   */
+  String readString(int length);
+
+  /**
+   * Reads the address and port pair from the kro server byte stream, which is a 6-byte series.
+   *
+   * @return The IP address and port pair
+   * @see InetAddressAndPort
+   * @throws UnknownHostException
+   */
+  InetAddressAndPort readInetAddressAndPort() throws UnknownHostException;
+
+  /**
+   * Skip N bytes
+   *
+   * @param length number of bytes to skip
+   */
+  void skip(int length);
+
+  /**
+   * @return the next 4-byte short in the stream
+   */
+  short readShortLE();
+
+  /**
+   * @return the next 4-byte integer in the stream
+   */
+  int readIntLE();
+
+  /**
+   * @return the next byte in the stream
+   */
+  byte readByte();
+
+  /**
+   * @return The underlying object that backs this packet reader, usually a byte buffer.
+   * @param <T> The underlying object type.
+   */
+  <T> T getUnderlyingObject();
+}
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java b/crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java
index 3a3caaa..3af7532 100644
--- a/crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/PacketRule.java
@@ -79,6 +79,10 @@ public record PacketRule(
     return new PacketRule(prefix, sizeTag, Type.DYNAMIC, clz);
   }
 
+  public static PacketRule Dynamic(int prefix, int sizeTag, Class<?> clz) {
+    return new PacketRule((short) prefix, sizeTag, Type.DYNAMIC, clz);
+  }
+
   /**
    * Utility constructor for building a non-packet version specific static packet whose name is
    * derived from the given class.
@@ -91,4 +95,8 @@ public record PacketRule(
   public static PacketRule Static(short prefix, int size, Class<?> clz) {
     return new PacketRule(prefix, size, Type.STATIC, clz);
   }
+
+  public static PacketRule Static(int prefix, int size, Class<?> clz) {
+    return new PacketRule((short) prefix, size, Type.STATIC, clz);
+  }
 }
diff --git a/crow.game.codec.api/src/main/java/crow/game/codec/package-info.java b/crow.game.codec.api/src/main/java/crow/game/codec/package-info.java
index b4b4345..bce2498 100644
--- a/crow.game.codec.api/src/main/java/crow/game/codec/package-info.java
+++ b/crow.game.codec.api/src/main/java/crow/game/codec/package-info.java
@@ -1,11 +1,7 @@
 /**
- * Package containing the base API objects for the protocol implementation.
- *
- * <p>This API is currently tied to Netty {@link io.netty.buffer.ByteBuf} but is otherwise decoupled
- * from Netty. sub-package contains the Netty-specific code for building {@link
- * crow.game.codec.Packet} objects.
- *
- * <p>TODO: switch to {@link java.nio.ByteBuffer} which can remove the netty dependency.
+ * Package containing the base API objects for the protocol implementation. It requires netty
+ * bytebuf, but it's slowly being phased out for PacketReader/PacketWriter interfaces which only
+ * support the subset of needed methods.
  *
  * <p>There are multiple hooks for handling differing packet versions in this API:
  *
@@ -18,12 +14,12 @@
  * <p>OR
  *
  * <p>You can define a standard packet "MyPacket" and {@link crow.game.codec.PacketHandler}
- * "MyPacketHandle" that, when you recieve calls into your handler, you can look at the current
+ * "MyPacketHandle" that, when you receive calls into your handler, you can look at the current
  * packet version via {@link crow.game.codec.Context}.supportedProtocolVersion and act accordingly
  * via branching logic.
  *
- * <p>Implementors note: It's a long shot but maybe it will be possible to support multiple protocol
- * versions in the same running server, via client version identification and pinning, then {@link
- * crow.game.codec.Context} may contain additional account and client info attached.
+ * <p>Implementors note: It's a long shot, but maybe it will be possible to support multiple
+ * protocol versions in the same running server, via client version identification and pinning, then
+ * {@link crow.game.codec.Context} may contain additional account and client info attached.
  */
 package crow.game.codec;
diff --git a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/ByteBufPacketReader.java b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/ByteBufPacketReader.java
new file mode 100644
index 0000000..5d2bbd7
--- /dev/null
+++ b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/ByteBufPacketReader.java
@@ -0,0 +1,66 @@
+package crow.game.codec.netty;
+
+import java.net.UnknownHostException;
+
+import io.netty.buffer.ByteBuf;
+
+import crow.game.codec.InetAddressAndPort;
+import crow.game.codec.PacketReader;
+
+/** Implementation of PacketReader that wraps a Netty {@link ByteBuf}. */
+public class ByteBufPacketReader implements PacketReader {
+
+  private final ByteBuf buf;
+
+  public ByteBufPacketReader(ByteBuf buf) {
+    this.buf = buf;
+  }
+
+  @Override
+  public InetAddressAndPort readInetAddressAndPort() throws UnknownHostException {
+    byte[] a = new byte[4];
+    buf.readBytes(a, 0, 4);
+
+    int port = buf.readShort();
+    return new InetAddressAndPort(a, port);
+  }
+
+  @Override
+  public void skip(int length) {
+    buf.skipBytes(length);
+  }
+
+  @Override
+  public String readString(int length) {
+    byte x = 0;
+    StringBuffer out = new StringBuffer();
+
+    do {
+      x = buf.readByte();
+      if (x != 0) {
+        out.append((char) x);
+      }
+    } while (--length > 0);
+    return out.toString();
+  }
+
+  @Override
+  public int readIntLE() {
+    return buf.readIntLE();
+  }
+
+  @Override
+  public short readShortLE() {
+    return buf.readShortLE();
+  }
+
+  @Override
+  public byte readByte() {
+    return buf.readByte();
+  }
+
+  @Override
+  public <T> T getUnderlyingObject() {
+    return (T) buf;
+  }
+}
diff --git a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java
index 670920a..5a18fd5 100644
--- a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java
+++ b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/FrameCodec.java
@@ -19,7 +19,9 @@ import crow.game.codec.PacketRule;
 /**
  * The FrameCodec converts to and from {@link ByteBuf} streams into individual {@link
  * crow.game.codec.Packet} objects. The first two bytes of a packet determine which type of packet
- * it is (called the packet ID).
+ * it is (called the packet ID). Packets which are sized dynamically will have their lengths read
+ * and written here as well, so individual {@link PacketHandler} implementations do not need to
+ * worry about length.
  *
  * @see crow.game.codec.PacketRule
  * @see crow.game.codec.Packet
@@ -27,9 +29,8 @@ import crow.game.codec.PacketRule;
  */
 public class FrameCodec extends ByteToMessageCodec<Packet> {
 
-  static Logger logger = LoggerFactory.getLogger(FrameCodec.class);
+  private static final Logger logger = LoggerFactory.getLogger(FrameCodec.class);
 
-  //
   protected final Context context;
 
   public FrameCodec(Context context) {
@@ -47,7 +48,7 @@ public class FrameCodec extends ByteToMessageCodec<Packet> {
   protected Optional<PacketRule> resolvePacketRule(Context ctx, int prefix, ByteBuf in) {
     return ctx.lookupPacketHandler(prefix).stream() //
         .map(((PacketHandler<?> ph) -> ph.rule(ctx)))
-        .filter((PacketRule pr) -> ctx.matchesPacketVersion(pr))
+        .filter(ctx::matchesPacketVersion)
         .filter((PacketRule pr) -> packetRuleHasMoreData(in, pr))
         .findFirst();
   }
@@ -103,4 +104,10 @@ public class FrameCodec extends ByteToMessageCodec<Packet> {
       out.add(new Packet(in.readBytes(size), rule.get()));
     }
   }
+
+  @Override
+  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+    logger.error("unhandled error: {}", cause.getMessage());
+    ctx.close();
+  }
 }
diff --git a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java
index e8585ce..9fb138a 100644
--- a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java
+++ b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/PacketCodec.java
@@ -7,6 +7,9 @@ import io.netty.buffer.Unpooled;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.MessageToMessageCodec;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import crow.game.codec.Context;
 import crow.game.codec.Packet;
 import crow.game.codec.PacketHandler;
@@ -16,6 +19,7 @@ import crow.game.codec.PacketHandler;
  * business-logic specific POJOs.
  */
 public class PacketCodec extends MessageToMessageCodec<Packet, Object> {
+  private static final Logger logger = LoggerFactory.getLogger(PacketCodec.class);
 
   protected final Context context;
 
@@ -35,7 +39,7 @@ public class PacketCodec extends MessageToMessageCodec<Packet, Object> {
             .orElseThrow());
 
     msg.buffer().readShortLE(); // drop the packet ID
-    out.add(h.fromPacket(wrCtx, msg.buffer()));
+    out.add(h.fromPacket(wrCtx, new ByteBufPacketReader(msg.buffer())));
   }
 
   @Override
@@ -57,4 +61,10 @@ public class PacketCodec extends MessageToMessageCodec<Packet, Object> {
     h.toPacket(wrCtx, directBuffer, msg);
     out.add(new Packet(directBuffer, h.rule(wrCtx)));
   }
+
+  @Override
+  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+    logger.error("unhandled error: {}", cause.getMessage());
+    ctx.close();
+  }
 }
diff --git a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/package-info.java b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/package-info.java
index 3d13680..66c8989 100644
--- a/crow.game.codec.netty/src/main/java/crow/game/codec/netty/package-info.java
+++ b/crow.game.codec.netty/src/main/java/crow/game/codec/netty/package-info.java
@@ -1,2 +1,5 @@
-/** Package containing all the serialization and deserialization logic for the protocol. */
+/**
+ * Package containing all the netty-specific serialization and deserialization logic for the
+ * protocol.
+ */
 package crow.game.codec.netty;
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java
index 9141c1a..d583967 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PingPacketHandler.java
@@ -4,6 +4,7 @@ import io.netty.buffer.ByteBuf;
 
 import crow.game.codec.Context;
 import crow.game.codec.PacketHandler;
+import crow.game.codec.PacketReader;
 import crow.game.codec.PacketRule;
 
 /**
@@ -14,7 +15,7 @@ import crow.game.codec.PacketRule;
 public class PingPacketHandler implements PacketHandler<Ping> {
 
   @Override
-  public Ping fromPacket(Context ctx, ByteBuf buffer) throws Exception {
+  public Ping fromPacket(Context ctx, PacketReader buffer) throws Exception {
     return new Ping();
   }
 
diff --git a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java
index 1d67072..214d4de 100644
--- a/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java
+++ b/crow.game.examples.pingpong/src/main/java/crow/game/examples/pingpong/proto/PongPacketHandler.java
@@ -4,6 +4,7 @@ import io.netty.buffer.ByteBuf;
 
 import crow.game.codec.Context;
 import crow.game.codec.PacketHandler;
+import crow.game.codec.PacketReader;
 import crow.game.codec.PacketRule;
 
 /**
@@ -14,7 +15,7 @@ import crow.game.codec.PacketRule;
 public class PongPacketHandler implements PacketHandler<Pong> {
 
   @Override
-  public Pong fromPacket(Context ctx, ByteBuf buffer) {
+  public Pong fromPacket(Context ctx, PacketReader buffer) {
     return new Pong();
   }