Public Repository for the Magicbane Shadowbane Emulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
7.4 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.net;
import engine.exception.SerializationException;
import engine.net.client.Protocol;
import engine.server.MBServerStatics;
import engine.util.StringUtils;
import org.pmw.tinylog.Logger;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
/**
* This class represents the NetMsgs set to/from the SBClient and in between
* MBServer Server Suite components. Note that since the NetMsgs sent to/from
* the SBClient do NOT include a MsgLen or DataLen parameter, special
* serialization/deserialization must be implemented.
*
*/
public abstract class AbstractNetMsg {
protected final Protocol protocolMsg;
private AbstractConnection origin;
private static ConcurrentHashMap<Protocol, NetMsgStat> stats = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
/**
* This is the general purpose constructor.
*
* @param protocolMsg
*/
protected AbstractNetMsg(Protocol protocolMsg) {
super();
this.protocolMsg = protocolMsg;
}
protected AbstractNetMsg(Protocol protocolMsg, AbstractConnection origin) {
super();
this.protocolMsg = protocolMsg;
this.origin = origin;
}
protected AbstractNetMsg(Protocol protocolMsg, AbstractNetMsg msg) {
super();
this.protocolMsg = protocolMsg;
this.origin = msg.origin;
}
/**
* This constructor is used by NetMsgFactory. It attempts to deserialize the
* ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
* past the limit) then this constructor Throws that Exception to the
* caller.
*
* @param reader
*/
protected AbstractNetMsg(Protocol protocolMsg, AbstractConnection origin,
ByteBufferReader reader)
{
this.protocolMsg = protocolMsg;
this.origin = origin;
// Call the subclass specific deserializer
try {
this._deserialize(reader);
} catch (NullPointerException e) {
Logger.error(e);
}
}
/**
* Deserializes the subclass specific items from the supplied
* ByteBufferReader
*
* @param reader
*/
protected abstract void _deserialize(ByteBufferReader reader);
/**
* Serializes the subclass specific items to the supplied ByteBufferWriter
*
* @param writer
* @throws Exception
*/
protected abstract void _serialize(ByteBufferWriter writer)
throws SerializationException;
/**
* Attempts to serialize this NetMsg into a ByteBuffer. ByteBuffer is
* obtained from a pool, so to retain max efficiency, the caller needs to
* return this BB to the pool. Header size and layout is entirely defined by
* the subclass of AbstractNetMsg
*
* @return a ByteBuffer
*/
public final ByteBuffer serialize() {
NetMsgStat stat;
if (!AbstractNetMsg.stats.containsKey(this.protocolMsg)) {
stat = new NetMsgStat(this.protocolMsg, this.getPowerOfTwoBufferSize());
AbstractNetMsg.stats.put(this.protocolMsg, stat);
} else
stat = AbstractNetMsg.stats.get(this.protocolMsg);
int lowerPow = stat.getMax();
int upperPow = lowerPow + 4;
ByteBuffer bb = null;
int startPos = 0;
ByteBufferWriter writer = null;
for (int i = lowerPow; i < upperPow; ++i) {
// get an appropriate sized BB from pool
bb = Network.byteBufferPool.getBuffer(i);
// Mark start position
startPos = bb.position();
// Make a writer
writer = new ByteBufferWriter(bb); // FIXME inefficient to
// Set aside header here.
AbstractNetMsg.allocHeader(writer, this.getHeaderSize());
// Now serialize the object's specifics
try {
this._serialize(writer);
//Serialize successful, update NetMsgStat
stat.updateStat(i);
} catch (BufferOverflowException boe) {
Logger.error("BufferSize PowerOfTwo: " + i
+ " is too small for " + protocolMsg != null? protocolMsg.name() : this.getClass().getName() + ", trying again with " + (i + 1));
//Return buffer.
Network.byteBufferPool.putBuffer(bb);
continue;
} catch (Exception e) {
//Return buffer.
Logger.error(e);
e.printStackTrace();
Network.byteBufferPool.putBuffer(bb);
return null;
}
// This shouldn't throw any errors since this part of the BB has
// already been allocated
this.writeHeaderAt(startPos, writer);
return writer.getBb();
}
// If we get here, its not a successful serialization and lastError
// should be set
return null;
}
private static void allocHeader(ByteBufferWriter writer, int bytes) {
byte zero = 0; // prevents the int->byte cast
for (int h = 0; h < bytes; ++h) {
writer.put(zero);
}
}
/**
* Function allows for setting other than default initial Buffer size for
* the Serializer. Override and return the size of the buffer in power of
* two bytes.
*
* Example, if you would like a buffer of 65535, then return 16 from this
* value since 2^16 = 65535
*
* @return the power to raise two to.
*/
protected int getPowerOfTwoBufferSize() {
return (10); // 2^10 == 1024
}
/**
* Forces subclass to define how large (in bytes) the message's header is.
*
* @return the length (in bytes) of this message type's header.
*/
protected abstract int getHeaderSize();
/**
* Forces subclasses to implement how to write its own header into a
* byteBuffer
*
* @param startPos
* - starting position for the header write
* @param writer
* - ByteBufferWriter to write the header to.
*/
protected abstract void writeHeaderAt(int startPos, ByteBufferWriter writer);
/**
* @return The protocolMsg of this Msg.
*/
public Protocol getProtocolMsg() {
return protocolMsg;
}
/**
* @return The protocolMsg As a string.
*/
public String getOpcodeAsString() {
return StringUtils.toHexString(protocolMsg.opcode);
}
/**
* @return the origin
*/
public AbstractConnection getOrigin() {
return origin;
}
public void setOrigin(AbstractConnection conn) {
this.origin = conn;
}
}