From c714187eeea9307019b1413baa464022e49d4407 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 5 Apr 2020 07:07:30 +0800 Subject: [PATCH] Refactor Object Decoder/Encoder Prevent crash on netplay --- .../java/forge/net/CObjectInputStream.java | 55 ++++++++++++++++++ .../java/forge/net/CObjectOutputStream.java | 31 ++++++++++ .../forge/net/CompatibleObjectDecoder.java | 46 +++++++++++++++ .../forge/net/CompatibleObjectEncoder.java | 37 ++++++++++++ .../java/forge/net/CustomObjectDecoder.java | 58 ------------------- .../java/forge/net/CustomObjectEncoder.java | 56 ------------------ .../java/forge/net/GameProtocolHandler.java | 18 ++++-- .../main/java/forge/net/ProtocolMethod.java | 32 ++++------ .../java/forge/net/client/FGameClient.java | 8 +-- .../java/forge/net/server/FServerManager.java | 8 +-- 10 files changed, 201 insertions(+), 148 deletions(-) create mode 100644 forge-gui/src/main/java/forge/net/CObjectInputStream.java create mode 100644 forge-gui/src/main/java/forge/net/CObjectOutputStream.java create mode 100644 forge-gui/src/main/java/forge/net/CompatibleObjectDecoder.java create mode 100644 forge-gui/src/main/java/forge/net/CompatibleObjectEncoder.java delete mode 100644 forge-gui/src/main/java/forge/net/CustomObjectDecoder.java delete mode 100644 forge-gui/src/main/java/forge/net/CustomObjectEncoder.java diff --git a/forge-gui/src/main/java/forge/net/CObjectInputStream.java b/forge-gui/src/main/java/forge/net/CObjectInputStream.java new file mode 100644 index 00000000000..b6d53c311ad --- /dev/null +++ b/forge-gui/src/main/java/forge/net/CObjectInputStream.java @@ -0,0 +1,55 @@ +package forge.net; + +import io.netty.handler.codec.serialization.ClassResolver; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.io.StreamCorruptedException; + +public class CObjectInputStream extends ObjectInputStream { + private final ClassResolver classResolver; + + CObjectInputStream(InputStream in, ClassResolver classResolver) throws IOException { + super(in); + this.classResolver = classResolver; + } + + protected void readStreamHeader() throws IOException { + int version = this.readByte() & 255; + if (version != 5) { + throw new StreamCorruptedException("Unsupported version: " + version); + } + } + + protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { + int type = this.read(); + if (type < 0) { + throw new EOFException(); + } else { + switch(type) { + case 0: + return super.readClassDescriptor(); + case 1: + String className = this.readUTF(); + Class clazz = this.classResolver.resolve(className); + return ObjectStreamClass.lookupAny(clazz); + default: + throw new StreamCorruptedException("Unexpected class descriptor type: " + type); + } + } + } + + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + Class clazz; + try { + clazz = this.classResolver.resolve(desc.getName()); + } catch (ClassNotFoundException var4) { + clazz = super.resolveClass(desc); + } + + return clazz; + } +} diff --git a/forge-gui/src/main/java/forge/net/CObjectOutputStream.java b/forge-gui/src/main/java/forge/net/CObjectOutputStream.java new file mode 100644 index 00000000000..b086ad87827 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/CObjectOutputStream.java @@ -0,0 +1,31 @@ +package forge.net; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; + +public class CObjectOutputStream extends ObjectOutputStream { + static final int TYPE_FAT_DESCRIPTOR = 0; + static final int TYPE_THIN_DESCRIPTOR = 1; + + CObjectOutputStream(OutputStream out) throws IOException { + super(out); + } + + protected void writeStreamHeader() throws IOException { + this.writeByte(5); + } + + protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException { + Class clazz = desc.forClass(); + if (!clazz.isPrimitive() && !clazz.isArray() && !clazz.isInterface() && desc.getSerialVersionUID() != 0L) { + this.write(1); + this.writeUTF(desc.getName()); + } else { + this.write(0); + super.writeClassDescriptor(desc); + } + + } +} diff --git a/forge-gui/src/main/java/forge/net/CompatibleObjectDecoder.java b/forge-gui/src/main/java/forge/net/CompatibleObjectDecoder.java new file mode 100644 index 00000000000..ff836965c88 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/CompatibleObjectDecoder.java @@ -0,0 +1,46 @@ +package forge.net; + +import forge.GuiBase; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.serialization.ClassResolver; + +import java.io.ObjectInputStream; +import java.io.StreamCorruptedException; + +public class CompatibleObjectDecoder extends LengthFieldBasedFrameDecoder { + private final ClassResolver classResolver; + + public CompatibleObjectDecoder(ClassResolver classResolver) { + this(1048576, classResolver); + } + + public CompatibleObjectDecoder(int maxObjectSize, ClassResolver classResolver) { + super(maxObjectSize, 0, 4, 0, 4); + this.classResolver = classResolver; + } + + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + ByteBuf frame = (ByteBuf)super.decode(ctx, in); + if (frame == null) { + return null; + } else { + ObjectInputStream ois = GuiBase.hasPropertyConfig() ? + new ObjectInputStream(new ByteBufInputStream(frame, true)): + new CObjectInputStream(new ByteBufInputStream(frame, true),this.classResolver); + + Object var5 = null; + try { + var5 = ois.readObject(); + } catch (StreamCorruptedException e) { + e.printStackTrace(); + } finally { + ois.close(); + } + + return var5; + } + } +} diff --git a/forge-gui/src/main/java/forge/net/CompatibleObjectEncoder.java b/forge-gui/src/main/java/forge/net/CompatibleObjectEncoder.java new file mode 100644 index 00000000000..a1060ed1a30 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/CompatibleObjectEncoder.java @@ -0,0 +1,37 @@ +package forge.net; + +import forge.GuiBase; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +import java.io.ObjectOutputStream; +import java.io.Serializable; + +public class CompatibleObjectEncoder extends MessageToByteEncoder { + private static final byte[] LENGTH_PLACEHOLDER = new byte[4]; + + @Override + protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception { + int startIdx = out.writerIndex(); + ByteBufOutputStream bout = new ByteBufOutputStream(out); + ObjectOutputStream oout = null; + + try { + bout.write(LENGTH_PLACEHOLDER); + oout = GuiBase.hasPropertyConfig() ? new ObjectOutputStream(bout) : new CObjectOutputStream(bout); + oout.writeObject(msg); + oout.flush(); + } finally { + if (oout != null) { + oout.close(); + } else { + bout.close(); + } + } + + int endIdx = out.writerIndex(); + out.setInt(startIdx, endIdx - startIdx - 4); + } +} diff --git a/forge-gui/src/main/java/forge/net/CustomObjectDecoder.java b/forge-gui/src/main/java/forge/net/CustomObjectDecoder.java deleted file mode 100644 index 47d898769d4..00000000000 --- a/forge-gui/src/main/java/forge/net/CustomObjectDecoder.java +++ /dev/null @@ -1,58 +0,0 @@ -package forge.net; - -import forge.GuiBase; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.LengthFieldBasedFrameDecoder; -import io.netty.handler.codec.serialization.ClassResolver; -import org.mapdb.elsa.ElsaObjectInputStream; - -import java.io.ObjectInputStream; - -public class CustomObjectDecoder extends LengthFieldBasedFrameDecoder { - private final ClassResolver classResolver; - - public CustomObjectDecoder(ClassResolver classResolver) { - this(1048576, classResolver); - } - - public CustomObjectDecoder(int maxObjectSize, ClassResolver classResolver) { - super(maxObjectSize, 0, 4, 0, 4); - this.classResolver = classResolver; - } - - protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { - ByteBuf frame = (ByteBuf) super.decode(ctx, in); - if (frame == null) { - return null; - } else { - if (GuiBase.hasPropertyConfig()){ - ElsaObjectInputStream ois = new ElsaObjectInputStream(new ByteBufInputStream(frame, true)); - - Object var5; - try { - var5 = ois.readObject(); - } finally { - ois.close(); - } - - return var5; - } - else { - ObjectInputStream ois = new ObjectInputStream(new ByteBufInputStream(frame, true)); - - Object var5; - try { - var5 = ois.readObject(); - } finally { - ois.close(); - } - - return var5; - } - } - } - - public static int maxObjectsize = 10000000; //10megabyte??? -} diff --git a/forge-gui/src/main/java/forge/net/CustomObjectEncoder.java b/forge-gui/src/main/java/forge/net/CustomObjectEncoder.java deleted file mode 100644 index 58dc82a58fb..00000000000 --- a/forge-gui/src/main/java/forge/net/CustomObjectEncoder.java +++ /dev/null @@ -1,56 +0,0 @@ -package forge.net; - -import forge.GuiBase; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; -import org.mapdb.elsa.ElsaObjectOutputStream; - -import java.io.ObjectOutputStream; -import java.io.Serializable; - -public class CustomObjectEncoder extends MessageToByteEncoder { - private static final byte[] LENGTH_PLACEHOLDER = new byte[4]; - - public CustomObjectEncoder() { - } - - protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception { - int startIdx = out.writerIndex(); - ByteBufOutputStream bout = new ByteBufOutputStream(out); - - if (GuiBase.hasPropertyConfig()){ - ElsaObjectOutputStream oout = null; - try { - bout.write(LENGTH_PLACEHOLDER); - oout = new ElsaObjectOutputStream(bout); - oout.writeObject(msg); - oout.flush(); - } finally { - if (oout != null) { - oout.close(); - } else { - bout.close(); - } - } - } else { - ObjectOutputStream oout = null; - try { - bout.write(LENGTH_PLACEHOLDER); - oout = new ObjectOutputStream(bout); - oout.writeObject(msg); - oout.flush(); - } finally { - if (oout != null) { - oout.close(); - } else { - bout.close(); - } - } - } - - int endIdx = out.writerIndex(); - out.setInt(startIdx, endIdx - startIdx - 4); - } -} diff --git a/forge-gui/src/main/java/forge/net/GameProtocolHandler.java b/forge-gui/src/main/java/forge/net/GameProtocolHandler.java index face68c68ac..d22fc8bdf33 100644 --- a/forge-gui/src/main/java/forge/net/GameProtocolHandler.java +++ b/forge-gui/src/main/java/forge/net/GameProtocolHandler.java @@ -1,8 +1,10 @@ package forge.net; import forge.FThreads; +import forge.assets.FSkinProp; import forge.net.event.GuiGameEvent; import forge.net.event.ReplyEvent; +import forge.util.gui.SOptionPane; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; @@ -25,6 +27,7 @@ public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapte @Override public final void channelRead(final ChannelHandlerContext ctx, final Object msg) { + final String[] catchedError = {""}; System.out.println("Received: " + msg); if (msg instanceof ReplyEvent) { final ReplyEvent event = (ReplyEvent) msg; @@ -36,7 +39,9 @@ public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapte final Method method = protocolMethod.getMethod(); if (method == null) { - throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name())); + //throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name())); + catchedError[0] += String.format("IllegalStateException: Method %s not found (GameProtocolHandler.java Line 43)\n", protocolMethod.name()); + System.err.println(String.format("Method %s not found", protocolMethod.name())); } final Object[] args = event.getObjects(); @@ -56,7 +61,9 @@ public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapte } catch (final IllegalAccessException | IllegalArgumentException e) { System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length)); } catch (final InvocationTargetException e) { - throw new RuntimeException(e.getTargetException()); + //throw new RuntimeException(e.getTargetException()); + catchedError[0] += (String.format("RuntimeException: %s (GameProtocolHandler.java Line 65)\n", e.getTargetException().toString())); + System.err.println(e.getTargetException().toString()); } } else { Serializable reply = null; @@ -70,8 +77,11 @@ public abstract class GameProtocolHandler extends ChannelInboundHandlerAdapte } } catch (final IllegalAccessException | IllegalArgumentException e) { System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length)); - } catch (final InvocationTargetException e) { - throw new RuntimeException(e.getTargetException()); + } catch (final NullPointerException | InvocationTargetException e) { + //throw new RuntimeException(e.getTargetException()); + catchedError[0] += e.toString(); + SOptionPane.showMessageDialog(catchedError[0], "Error", FSkinProp.ICO_WARNING); + System.err.println(e.toString()); } getRemote(ctx).send(new ReplyEvent(event.getId(), reply)); } diff --git a/forge-gui/src/main/java/forge/net/ProtocolMethod.java b/forge-gui/src/main/java/forge/net/ProtocolMethod.java index 642e79dec5c..f34d35fdb8e 100644 --- a/forge-gui/src/main/java/forge/net/ProtocolMethod.java +++ b/forge-gui/src/main/java/forge/net/ProtocolMethod.java @@ -1,7 +1,6 @@ package forge.net; import com.google.common.base.Function; -import forge.GuiBase; import forge.assets.FSkinProp; import forge.deck.CardPool; import forge.game.GameEntityView; @@ -18,12 +17,9 @@ import forge.player.PlayerZoneUpdates; import forge.trackable.TrackableCollection; import forge.util.ITriggerEvent; import forge.util.ReflectionUtil; -import org.apache.commons.lang3.SerializationUtils; -import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; -import java.util.ConcurrentModificationException; import java.util.List; import java.util.Map; @@ -159,24 +155,15 @@ public enum ProtocolMethod { } public void checkArgs(final Object[] args) { - if (GuiBase.hasPropertyConfig()) - return; //uses custom serializer for Android 8+.. for (int iArg = 0; iArg < args.length; iArg++) { - Object arg = null; - Class type = null; - try { - arg = args[iArg]; - if (this.args.length > iArg) - type = this.args[iArg]; + final Object arg = args[iArg]; + final Class type = this.args[iArg]; + if (!ReflectionUtil.isInstance(arg, type)) { + //throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName())); + System.err.println(String.format("InternalError: Protocol method %s: illegal argument (%d) of type %s, %s expected (ProtocolMethod.java Line 163)", name(), iArg, arg.getClass().getName(), type.getName())); } - catch (ArrayIndexOutOfBoundsException ex){ ex.printStackTrace(); } - catch(ConcurrentModificationException ex) { ex.printStackTrace(); } - if (arg != null) - if (type != null) - if (!ReflectionUtil.isInstance(arg, type)) { - throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName())); - } - if (arg != null) { + //this should be handled via decoder or it will process them twice + /*if (arg != null) { // attempt to Serialize each argument, this will throw an exception if it can't. try { byte[] serialized = SerializationUtils.serialize((Serializable) arg); @@ -189,7 +176,7 @@ public enum ProtocolMethod { // can't seem to avoid this from periodically happening ex.printStackTrace(); } - } + }*/ } } @@ -199,7 +186,8 @@ public enum ProtocolMethod { return; } if (!ReflectionUtil.isInstance(value, returnType)) { - throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName())); + //throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName())); + System.err.println(String.format("IllegalStateException: Protocol method %s: illegal return object type %s returned by client, expected %s (ProtocolMethod.java Line 190)", name(), value.getClass().getName(), getReturnType().getName())); } } } diff --git a/forge-gui/src/main/java/forge/net/client/FGameClient.java b/forge-gui/src/main/java/forge/net/client/FGameClient.java index 44a1a7d79d5..740d78a5c08 100644 --- a/forge-gui/src/main/java/forge/net/client/FGameClient.java +++ b/forge-gui/src/main/java/forge/net/client/FGameClient.java @@ -1,5 +1,7 @@ package forge.net.client; +import forge.net.CompatibleObjectDecoder; +import forge.net.CompatibleObjectEncoder; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -26,8 +28,6 @@ import forge.net.event.IdentifiableNetEvent; import forge.net.event.LobbyUpdateEvent; import forge.net.event.MessageEvent; import forge.net.event.NetEvent; -import io.netty.handler.codec.serialization.ObjectDecoder; -import io.netty.handler.codec.serialization.ObjectEncoder; public class FGameClient implements IToServer { @@ -58,8 +58,8 @@ public class FGameClient implements IToServer { public void initChannel(final SocketChannel ch) throws Exception { final ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast( - new ObjectEncoder(), - new ObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), + new CompatibleObjectEncoder(), + new CompatibleObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), new MessageHandler(), new LobbyUpdateHandler(), new GameClientHandler(FGameClient.this)); diff --git a/forge-gui/src/main/java/forge/net/server/FServerManager.java b/forge-gui/src/main/java/forge/net/server/FServerManager.java index 68e4f7d1bbd..1e0ad247eb4 100644 --- a/forge-gui/src/main/java/forge/net/server/FServerManager.java +++ b/forge-gui/src/main/java/forge/net/server/FServerManager.java @@ -6,6 +6,8 @@ import forge.interfaces.IGuiGame; import forge.interfaces.ILobbyListener; import forge.match.LobbySlot; import forge.match.LobbySlotType; +import forge.net.CompatibleObjectDecoder; +import forge.net.CompatibleObjectEncoder; import forge.net.event.LobbyUpdateEvent; import forge.net.event.LoginEvent; import forge.net.event.LogoutEvent; @@ -24,8 +26,6 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.serialization.ClassResolvers; -import io.netty.handler.codec.serialization.ObjectDecoder; -import io.netty.handler.codec.serialization.ObjectEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; @@ -99,8 +99,8 @@ public final class FServerManager { public final void initChannel(final SocketChannel ch) throws Exception { final ChannelPipeline p = ch.pipeline(); p.addLast( - new ObjectEncoder(), - new ObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), + new CompatibleObjectEncoder(), + new CompatibleObjectDecoder(9766*1024, ClassResolvers.cacheDisabled(null)), new MessageHandler(), new RegisterClientHandler(), new LobbyInputHandler(),