diff options
Diffstat (limited to 'mod/src/main/java/moe/ymc/acron/net/WSFrameHandler.java')
-rw-r--r-- | mod/src/main/java/moe/ymc/acron/net/WSFrameHandler.java | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/mod/src/main/java/moe/ymc/acron/net/WSFrameHandler.java b/mod/src/main/java/moe/ymc/acron/net/WSFrameHandler.java new file mode 100644 index 0000000..912e73a --- /dev/null +++ b/mod/src/main/java/moe/ymc/acron/net/WSFrameHandler.java @@ -0,0 +1,174 @@ +package moe.ymc.acron.net; + +import com.google.gson.JsonParseException; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.websocketx.*; +import moe.ymc.acron.MinecraftServerHolder; +import moe.ymc.acron.auth.Action; +import moe.ymc.acron.auth.PolicyChecker; +import moe.ymc.acron.c2s.ReqCmd; +import moe.ymc.acron.c2s.ReqSetConfig; +import moe.ymc.acron.c2s.Request; +import moe.ymc.acron.cmd.CmdQueue; +import moe.ymc.acron.jvav.Pair; +import moe.ymc.acron.s2c.Event; +import moe.ymc.acron.s2c.EventQueue; +import moe.ymc.acron.s2c.response.EventError; +import moe.ymc.acron.s2c.response.EventOk; +import moe.ymc.acron.serialization.Serializer; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +/** + * The handler for WebSocket requests. + */ +public class WSFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> { + private static final Logger LOGGER = LogManager.getLogger(); + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + super.handlerAdded(ctx); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception { + LOGGER.debug("channelRead0: {} {}", + this, + ctx.channel()); + final WebSocketServerHandshaker handshaker = + ctx.channel().attr(Attributes.HANDSHAKER).get(); + if (msg instanceof CloseWebSocketFrame) { + handshaker.close(ctx.channel(), (CloseWebSocketFrame) msg.retain()); + return; + } + if (msg instanceof PingWebSocketFrame) { + ctx.write(new PongWebSocketFrame(msg.content().retain())); + return; + } + if (msg instanceof BinaryWebSocketFrame) { + throw new UnsupportedOperationException("Only text frames are accepted."); + } + final TextWebSocketFrame frame = (TextWebSocketFrame) msg; + + final ClientIdentification identification = ctx.channel().attr(Attributes.ID).get(); + final ClientConfiguration configuration = ctx.channel().attr(Attributes.CONFIGURATION).get(); + int id; + final Request request; + try { + request = Serializer.deserialize(frame); + id = request.getId(); + } catch (JsonParseException | IllegalArgumentException | IllegalStateException e) { + ctx.channel().writeAndFlush( + Serializer.serialize(new EventError(-2, EventError.Code.BAD_REQUEST.value, e.getMessage())) + ); + return; + } + try { + ctx.channel().writeAndFlush(Serializer.serialize(handle(request, + identification, + configuration, + ctx.channel()))); + } catch (Throwable e) { + LOGGER.info("An error occurred while processing the request. " + + "This may just be a malformed request. " + + "It is reported to the client.", + e); + ctx.channel().writeAndFlush( + Serializer.serialize(new EventError(id, EventError.Code.SERVER_ERROR.value, e.getMessage())) + ); + } + } + + @NotNull + private Event handle(@NotNull Request request, + @NotNull ClientIdentification identification, + @NotNull ClientConfiguration configuration, + @NotNull Channel channel) throws Throwable { + if (request instanceof final ReqCmd reqCmd) { + LOGGER.info("Client {} executed a command: `{}`.", + identification.client().id(), + reqCmd.cmd()); + final Pair<Action, Boolean> res = PolicyChecker.check(identification.client(), + reqCmd.cmd()); + if (res.l() == Action.DENY) { + return new EventError(reqCmd.id(), + EventError.Code.FORBIDDEN.value, "This client is not allowed to " + + "execute this command."); + } + // TODO: Ok event may be sent after executing the command. + CmdQueue.enqueue(reqCmd.id(), + res.r(), + channel, + reqCmd.config() == null ? + configuration : + convertConfiguration(reqCmd.config()), + reqCmd.cmd()); + return new EventOk(request.getId()); + } else if (request instanceof final ReqSetConfig reqSetConfig) { + channel.attr(Attributes.CONFIGURATION).set(convertConfiguration(reqSetConfig)); + return new EventOk(request.getId()); + } + // This should not occur. + throw new IllegalStateException("This should not occur."); + } + + private ClientConfiguration convertConfiguration(@NotNull ReqSetConfig request) { + final ServerWorld world; + if (request.world() != null) { + switch (request.world()) { + case OVERWORLD -> world = MinecraftServerHolder.getServer().getWorld(World.OVERWORLD); + case NETHER -> world = MinecraftServerHolder.getServer().getWorld(World.NETHER); + case END -> world = MinecraftServerHolder.getServer().getWorld(World.END); + default -> throw new IllegalArgumentException(); + } + } else { + world = MinecraftServerHolder.getServer().getOverworld(); + } + if (world == null) { + throw new IllegalStateException(String.format("The requested world %s is not available at this time.", + request.world())); + } + return new ClientConfiguration( + request.pos() == null ? + Vec3d.of(world.getSpawnPos()) : + new Vec3d(request.pos().x(), request.pos().y(), request.pos().z()), + request.rot() == null ? + Vec2f.ZERO : + new Vec2f(request.rot().x(), request.rot().y()), + world, + request.name() == null ? this.toString() : request.name() + ); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + LOGGER.debug("handshakeComplete: {} {}", + this, + ctx.channel()); + if (evt instanceof HandshakeComplete) { + final ClientIdentification identification = ctx.channel().attr(Attributes.ID).get(); + LOGGER.info("Client {} connected. It has {} rules with {} policy mode.", + identification.client().id(), + identification.client().rules().length, + identification.client().policyMode()); + final ServerWorld defaultWorld = MinecraftServerHolder.getServer().getOverworld(); + if (defaultWorld == null) { + throw new IllegalStateException("The default world is not available at this time."); + } + final ClientConfiguration configuration = + new ClientConfiguration(defaultWorld, + identification.client().id()); + ctx.channel().attr(Attributes.CONFIGURATION).set(configuration); + EventQueue.registerMessageRecipient(ctx.channel()); + } else { + ctx.fireUserEventTriggered(evt); + } + } +} |