/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.network.vanilla.message.codec.play; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.Sets; import io.netty.handler.codec.CodecException; import io.netty.handler.codec.DecoderException; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import org.lanternpowered.server.network.buffer.ByteBuffer; import org.lanternpowered.server.network.buffer.ByteBufferAllocator; import org.lanternpowered.server.network.message.Message; import org.lanternpowered.server.network.message.NullMessage; import org.lanternpowered.server.network.message.codec.Codec; import org.lanternpowered.server.network.message.codec.CodecContext; import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayInOutChannelPayload; import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayInOutRegisterChannels; import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayInOutUnregisterChannels; import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.Set; public abstract class AbstractCodecPlayInOutCustomPayload implements Codec<Message> { private static final AttributeKey<MultiPartMessage> FML_MULTI_PART_MESSAGE = AttributeKey.valueOf("fml-mpm"); @Override public ByteBuffer encode(CodecContext context, Message message) throws CodecException { final ByteBuffer buf = context.byteBufAlloc().buffer(); final String channel; final ByteBuffer content; if (message instanceof MessagePlayInOutChannelPayload) { final MessagePlayInOutChannelPayload message1 = (MessagePlayInOutChannelPayload) message; content = message1.getContent(); channel = message1.getChannel(); } else if (message instanceof MessagePlayInOutRegisterChannels) { content = encodeChannels(((MessagePlayInOutRegisterChannels) message).getChannels()); channel = "REGISTER"; } else if (message instanceof MessagePlayInOutUnregisterChannels) { content = encodeChannels(((MessagePlayInOutUnregisterChannels) message).getChannels()); channel = "UNREGISTER"; } else { final MessageResult result = encode0(context, message); channel = result.channel; content = result.byteBuf; } buf.writeString(channel); buf.writeBytes(content); return buf; } @Override public Message decode(CodecContext context, ByteBuffer buf) throws CodecException { String channel = buf.readString(); int length = buf.available(); if (length > Short.MAX_VALUE) { throw new DecoderException("CustomPayload messages may not be longer then " + Short.MAX_VALUE + " bytes"); } ByteBuffer content = buf.slice(); if ("REGISTER".equals(channel)) { Set<String> channels = decodeChannels(content); Iterator<String> it = channels.iterator(); while (it.hasNext()) { String channel0 = it.next(); if (channel0.startsWith("FML")) { it.remove(); } } if (!channels.isEmpty()) { return new MessagePlayInOutRegisterChannels(channels); } } else if ("UNREGISTER".equals(channel)) { Set<String> channels = decodeChannels(content); Iterator<String> it = channels.iterator(); while (it.hasNext()) { String channel0 = it.next(); if (channel0.startsWith("FML")) { it.remove(); } } if (!channels.isEmpty()) { return new MessagePlayInOutUnregisterChannels(channels); } } else if ("FML|MP".equals(channel)) { Attribute<MultiPartMessage> attribute = context.getChannel().attr(FML_MULTI_PART_MESSAGE); MultiPartMessage message0 = attribute.get(); if (message0 == null) { String channel0 = buf.readString(); int parts = buf.readByte() & 0xff; int size = content.readInteger(); if (size <= 0 || size >= -16797616) { throw new CodecException("Received FML MultiPart packet outside of valid length bounds, Max: -16797616, Received: " + size); } attribute.set(new MultiPartMessage(channel0, context.byteBufAlloc().buffer(size), parts)); } else { int part = buf.readByte() & 0xff; if (part != message0.index) { throw new CodecException("Received FML MultiPart packet out of order, Expected: " + message0.index + ", Got: " + part); } int len = content.available() - 1; content.readBytes(message0.buffer, message0.offset, len); message0.offset += len; message0.index++; if (message0.index >= message0.parts) { final Message message = decode0(context, message0.channel, message0.buffer); attribute.set(null); return message; } } } else { return decode0(context, channel, content); } return NullMessage.INSTANCE; } protected abstract MessageResult encode0(CodecContext context, Message message) throws CodecException; protected abstract Message decode0(CodecContext context, String channel, ByteBuffer content) throws CodecException; /** * Decodes the byte buffer into a set of channels. * * @param buffer the byte buffer * @return the channels */ private static Set<String> decodeChannels(ByteBuffer buffer) { byte[] bytes = new byte[buffer.available()]; buffer.readBytes(bytes); return Sets.newHashSet(Splitter.on('\u0000').split(new String(bytes, StandardCharsets.UTF_8))); } /** * Encodes the set of channels into a byte buffer. * * @param channels the channels * @return the byte buffer */ private static ByteBuffer encodeChannels(Set<String> channels) { return ByteBufferAllocator.unpooled().wrappedBuffer(Joiner.on('\u0000').join(channels).getBytes(StandardCharsets.UTF_8)); } protected static class MessageResult { private final String channel; private final ByteBuffer byteBuf; public MessageResult(String channel, ByteBuffer byteBuf) { this.byteBuf = byteBuf; this.channel = channel; } } private static class MultiPartMessage { private final String channel; private final ByteBuffer buffer; private final int parts; private int index; private int offset; MultiPartMessage(String channel, ByteBuffer buffer, int parts) { this.channel = channel; this.buffer = buffer; this.parts = parts; } } }