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.
417 lines
16 KiB
417 lines
16 KiB
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . |
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· |
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ |
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ |
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ |
|
// Magicbane Emulator Project © 2013 - 2022 |
|
// www.magicbane.com |
|
|
|
|
|
package engine.net; |
|
|
|
import engine.exception.FactoryBuildException; |
|
import engine.gameManager.ChatManager; |
|
import engine.net.client.ClientConnection; |
|
import engine.net.client.Protocol; |
|
import engine.net.client.msg.ErrorPopupMsg; |
|
import engine.net.client.msg.PlaceAssetMsg; |
|
import engine.objects.PlayerCharacter; |
|
import engine.server.MBServerStatics; |
|
import org.joda.time.DateTime; |
|
import org.pmw.tinylog.Logger; |
|
|
|
import java.lang.reflect.Constructor; |
|
import java.lang.reflect.InvocationTargetException; |
|
import java.nio.BufferUnderflowException; |
|
import java.nio.ByteBuffer; |
|
import java.util.ArrayList; |
|
import java.util.HashMap; |
|
|
|
public class NetMsgFactory { |
|
|
|
// NetMsg Opcode to Constructor List |
|
private static final HashMap<Integer, Constructor> netMsgDefinitions = new HashMap<>(); |
|
private static final int FLOOD_CONTROL_TRIP_SETPOINT = 1000; |
|
// Standardize the error strings |
|
private static String ALL_GOOD_JUST_NOT_ENOUGH_BYTES = "Not enough Bytes"; |
|
private static String DESERIALIZATION_FAILURE = "Deserialization Failure"; |
|
private static String UNIMPLEMENTED_OPCODE = "Unimplemented Opcode"; |
|
private static String UNKNOWN_OPCODE = "Unknown Opcode"; |
|
private final ArrayList<AbstractNetMsg> msgOutbox; |
|
private final AbstractConnection owner; |
|
protected ByteBuffer internalBuffer; |
|
private boolean enableFloodControl; |
|
private boolean bypassFloodControl; // temp bypass |
|
private boolean floodControlTripped; |
|
private int badOpcodeCount; |
|
private int lastMsgPosition = 0; |
|
|
|
public NetMsgFactory(AbstractConnection origin, boolean enableFloodControl) { |
|
this.internalBuffer = Network.byteBufferPool.getBuffer(18); //256k buffer |
|
|
|
this.bypassFloodControl = false; |
|
this.msgOutbox = new ArrayList<>(); |
|
this.enableFloodControl = enableFloodControl; |
|
this.floodControlTripped = false; |
|
this.owner = origin; |
|
} |
|
|
|
public NetMsgFactory(AbstractConnection origin) { |
|
this(origin, true); |
|
} |
|
|
|
public static String getByteArray(ByteBufferReader reader) { |
|
String ret = ""; |
|
if (reader == null) |
|
return ret; |
|
|
|
ByteBuffer bb = reader.getBb(); |
|
if (bb == null) |
|
return ret; |
|
|
|
int length = bb.limit(); // - bb.position(); |
|
ByteBuffer temp = bb.duplicate(); |
|
temp.position(bb.limit()); |
|
temp.flip(); |
|
for (int i = 0; i < length; i++) { |
|
ret += Integer.toString((temp.get() & 0xff) + 0x100, 16).substring(1).toUpperCase(); |
|
} |
|
return ret; |
|
} |
|
|
|
@SuppressWarnings("unchecked") |
|
private static AbstractNetMsg getNewInstanceOf(int opcode, |
|
AbstractConnection origin, ByteBufferReader reader) { |
|
try { |
|
|
|
Protocol protocolMsg = Protocol.getByOpcode(opcode); |
|
|
|
if (protocolMsg == Protocol.NONE) { |
|
|
|
String errorString = DateTime.now().toString() + origin.lastProtocol.name(); |
|
|
|
int errorCode = errorString.hashCode(); |
|
|
|
|
|
if (origin instanceof ClientConnection) { |
|
PlayerCharacter player = ((ClientConnection) origin).getPlayerCharacter(); |
|
if (player != null) { |
|
Logger.error("Invalid protocol msg for player " + player.getFirstName() + " : " + opcode + " lastopcode: " + origin.lastProtocol.name() + " Error Code : " + errorCode); |
|
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "Please Report What You Just Did. Ref Code: " + opcode); |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
origin.lastProtocol = protocolMsg; |
|
|
|
if (protocolMsg.constructor == null) { |
|
return null; |
|
} |
|
|
|
|
|
Constructor<AbstractNetMsg> constructor = protocolMsg.constructor; |
|
|
|
if (constructor == null) |
|
return null; |
|
|
|
Object[] myArgs = new Object[2]; |
|
myArgs[0] = origin; |
|
myArgs[1] = reader; |
|
|
|
Object object = constructor.newInstance(myArgs); |
|
|
|
if (object instanceof engine.net.AbstractNetMsg) |
|
return (AbstractNetMsg) object; |
|
|
|
} catch (IllegalAccessException | IllegalArgumentException | InstantiationException | |
|
ExceptionInInitializerError e) { |
|
Logger.error(e); |
|
|
|
} catch (InvocationTargetException e) { |
|
if (e.getCause() != null |
|
&& e.getCause().getClass() == BufferUnderflowException.class) |
|
throw new BufferUnderflowException(); |
|
Logger.error(e); |
|
} |
|
return null; |
|
} |
|
|
|
public final void addData(byte[] ba) { |
|
// Dont use prefab BB's here, since sizeof(ba) is unknown. |
|
ByteBuffer bb = ByteBuffer.wrap(ba); |
|
bb.position(bb.capacity()); |
|
this.addData(bb); |
|
} |
|
|
|
public final void addData(ByteBuffer newData) { |
|
synchronized (this.internalBuffer) { |
|
|
|
int newCapacity = this.internalBuffer.position() + newData.position(); |
|
|
|
if (newCapacity >= this.internalBuffer.capacity()) { |
|
//Resize!!!! |
|
Logger.warn( |
|
"Bytebuffer is being be Resized."); |
|
|
|
//Get a newer, bigger BB |
|
ByteBuffer newBB = Network.byteBufferPool.getBufferToFit((int) (newCapacity * 1.5)); |
|
|
|
//Copy old data in |
|
this.internalBuffer.flip(); |
|
newBB.put(this.internalBuffer); |
|
|
|
//Get a handle on old BB |
|
ByteBuffer oldBB = this.internalBuffer; |
|
|
|
//install new BB |
|
this.internalBuffer = newBB; |
|
|
|
//Return old BB |
|
Network.byteBufferPool.putBuffer(oldBB); |
|
} |
|
|
|
synchronized (newData) { |
|
// Copy over the data. |
|
newData.flip(); |
|
|
|
try { |
|
this.internalBuffer.put(newData); |
|
} catch (Exception e) { |
|
Logger.error(e.toString()); |
|
// TODO figure out how to handle this error. |
|
} |
|
} |
|
} |
|
} |
|
|
|
public void parseBuffer() { |
|
// Check flood control first |
|
if (this.floodControlTripped) |
|
// this.conn.disconnect(); |
|
return; |
|
|
|
// MBServer.jobMan.submitJob(new ParseBufferJob(this)); |
|
this._parseBuffer(); |
|
} |
|
|
|
/** |
|
* This function makes a copy of the current internal byte buffer and loads |
|
* the copy into a ByteBufferReader. It is copied so that the Factory can |
|
* continue to accumulate data on the internal buffer from the |
|
* socketChannels. The ByteBufferReader is then used in an attempt to build |
|
* an AbstractNetMsg subclass based on protocolMsg. If a message is successfully |
|
* built, the bytes used are removed from the Factory's internal byte |
|
* buffer. |
|
* |
|
* @return |
|
* @throws Exception |
|
*/ |
|
protected void _parseBuffer() { |
|
synchronized (this.internalBuffer) { |
|
while (this.internalBuffer.position() > 0) { |
|
// Check flood control first |
|
if (this.floodControlTripped) |
|
break; |
|
|
|
ByteBufferReader reader = null; |
|
|
|
// Check to see if the minimum amount of data is here: |
|
if (this.internalBuffer.position() < 4) |
|
// nothing wrong, just not enough info yet. |
|
break; |
|
|
|
// copy internal buffer into a reader |
|
reader = new ByteBufferReader(this.internalBuffer, false); |
|
|
|
// Reset the limit to the capacity |
|
this.internalBuffer.limit(this.internalBuffer.capacity()); |
|
|
|
try { |
|
AbstractNetMsg msg = this.tryBuild(owner, reader); |
|
|
|
// error, null messages are being returned on unhandled |
|
// opcodes |
|
// for some reason |
|
if (msg == null) |
|
throw new FactoryBuildException(UNIMPLEMENTED_OPCODE); |
|
|
|
|
|
if (owner.getClass().equals(ClientConnection.class)) { |
|
ClientConnection client = (ClientConnection) owner; |
|
client.setLastOpcode(msg.getProtocolMsg().opcode); |
|
} |
|
|
|
|
|
// Logger.debug("Adding a " + msg.getSimpleClassName() |
|
// + " to the outbox."); |
|
this.addMsgToOutBox(msg); |
|
|
|
this.dropLeadingBytesFromBuffer(reader.position()); |
|
this.bypassFloodControl = false; |
|
|
|
} catch (FactoryBuildException e) { |
|
String error = e.getMessage(); |
|
int readerPos = reader.position(); |
|
|
|
if (error.equals(ALL_GOOD_JUST_NOT_ENOUGH_BYTES)) { |
|
break; |
|
} |
|
// no worries, just break. |
|
|
|
else if (error.equals(DESERIALIZATION_FAILURE)) { |
|
// Lop readerPos bytes off the buffer. |
|
this.dropLeadingBytesFromBuffer(readerPos); |
|
|
|
// Lets bypass flood control for now. |
|
this.bypassFloodControl = true; |
|
continue; |
|
|
|
} else if (error.equals(UNIMPLEMENTED_OPCODE)) { |
|
|
|
if (owner.lastProtocol != null && owner.lastProtocol.constructor == null) { |
|
this.dropLeadingBytesFromBuffer(readerPos); |
|
this.bypassFloodControl = true; |
|
continue; |
|
} |
|
|
|
// Lop readerPos bytes off the buffer. |
|
if (reader.position() >= 4) |
|
reader.position(reader.position() - 4); |
|
int newPosition = Protocol.FindNextValidOpcode(reader); |
|
this.dropLeadingBytesFromBuffer(newPosition); |
|
// Lets bypass flood control for now. |
|
this.bypassFloodControl = true; |
|
|
|
continue; |
|
|
|
} else if (error.equals(UNKNOWN_OPCODE)) { |
|
|
|
if (owner.lastProtocol != null && owner.lastProtocol.constructor == null) { |
|
this.dropLeadingBytesFromBuffer(readerPos); |
|
this.bypassFloodControl = true; |
|
continue; |
|
} |
|
// We don't know what this is or how long, so dump the |
|
// first |
|
// byte and try again |
|
if (reader.position() >= 4) |
|
reader.position(reader.position() - 4); |
|
int newPosition = Protocol.FindNextValidOpcode(reader); |
|
this.dropLeadingBytesFromBuffer(newPosition); |
|
// Lets bypass flood control for now. |
|
this.bypassFloodControl = true; |
|
|
|
continue; |
|
} |
|
} catch (Exception e) { |
|
// TODO FIX THIS!!!! |
|
// Logger.error( e); |
|
|
|
}// end catch |
|
|
|
} // end while loop |
|
} |
|
}// end fn |
|
|
|
public AbstractNetMsg tryBuild(AbstractConnection origin, |
|
ByteBufferReader reader) throws FactoryBuildException { |
|
try { |
|
|
|
// Get the protocolMsg |
|
int opcode = reader.getInt(); |
|
// String ocHex = StringUtils.toHexString(protocolMsg); |
|
|
|
if (MBServerStatics.PRINT_INCOMING_OPCODES) |
|
try { |
|
Logger.info("Incoming protocolMsg: " |
|
+ Protocol.getByOpcode(opcode).name() + " " + Integer.toHexString(opcode) + ", size: " + reader.getBb().limit() + "; " + getByteArray(reader)); |
|
} catch (Exception e) { |
|
Logger.error(e); |
|
} |
|
|
|
return NetMsgFactory.getNewInstanceOf(opcode, origin, reader); |
|
|
|
} catch (BufferUnderflowException e) { |
|
// This is okay. it indicates that we recognized the protocolMsg, but |
|
// there isn't enough information in |
|
// the reader to complete the NetMsg deserialization |
|
throw new FactoryBuildException(ALL_GOOD_JUST_NOT_ENOUGH_BYTES); |
|
|
|
} |
|
} |
|
|
|
private void incrBadOpcodeCount() { |
|
// keeping this a nested if for Troubleshooting/clarity |
|
if (this.enableFloodControl == true) |
|
if (this.bypassFloodControl == false) { |
|
++this.badOpcodeCount; |
|
|
|
|
|
if (this.badOpcodeCount >= FLOOD_CONTROL_TRIP_SETPOINT) { |
|
if (this.owner != null) { |
|
if (this.owner instanceof ClientConnection) { |
|
ClientConnection client = (ClientConnection) this.owner; |
|
if (client.getPlayerCharacter() != null) { |
|
ChatManager.chatSystemError(client.getPlayerCharacter(), "TRIPPED Flood Control! PLEASE RELOG!"); |
|
Logger.info(client.getPlayerCharacter().getName() + " Tripped Flood Control!" + this.badOpcodeCount); |
|
} |
|
|
|
} |
|
} |
|
this.floodControlTripped = true; |
|
} else { |
|
if (this.owner != null) { |
|
if (this.owner instanceof ClientConnection) { |
|
ClientConnection client = (ClientConnection) this.owner; |
|
if (client.getPlayerCharacter() != null) { |
|
ChatManager.chatSystemError(client.getPlayerCharacter(), "Client sending bad messages. bad message Count " + this.badOpcodeCount); |
|
Logger.info(client.getPlayerCharacter().getName() + " has been caught sending bad opcodes. Bad Opcode Count " + this.badOpcodeCount); |
|
} |
|
|
|
} |
|
|
|
|
|
} |
|
} |
|
|
|
} |
|
} |
|
|
|
protected final void dropLeadingBytesFromBuffer(int numberOfBytes) { |
|
this.internalBuffer.limit(this.internalBuffer.position()); |
|
this.internalBuffer.position(numberOfBytes); |
|
this.internalBuffer.compact(); // Compact |
|
} |
|
|
|
protected boolean addMsgToOutBox(AbstractNetMsg msg) { |
|
synchronized (this.msgOutbox) { |
|
return msgOutbox.add(msg); |
|
} |
|
} |
|
|
|
public AbstractNetMsg getMsg() { |
|
synchronized (this.msgOutbox) { |
|
if (this.msgOutbox.isEmpty()) |
|
return null; |
|
return msgOutbox.remove(0); |
|
} |
|
} |
|
|
|
public boolean hasMsg() { |
|
synchronized (this.msgOutbox) { |
|
return !msgOutbox.isEmpty(); |
|
} |
|
} |
|
|
|
public boolean hasData() { |
|
synchronized (this.internalBuffer) { |
|
return (this.internalBuffer.position() != 0); |
|
} |
|
} |
|
|
|
public ByteBuffer getInternalBuffer() { |
|
return internalBuffer; |
|
} |
|
|
|
}
|
|
|