package com.aionemu.packetsamurai.parser;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Iterator;
import java.util.List;
import com.aionemu.packetsamurai.PacketSamurai;
import com.aionemu.packetsamurai.Util;
import com.aionemu.packetsamurai.parser.datatree.*;
import com.aionemu.packetsamurai.parser.datatree.IntBCValuePart;
import com.aionemu.packetsamurai.parser.formattree.ForPart;
import com.aionemu.packetsamurai.parser.formattree.Format;
import com.aionemu.packetsamurai.parser.formattree.Part;
import com.aionemu.packetsamurai.parser.formattree.PartContainer;
import com.aionemu.packetsamurai.parser.formattree.SwitchCaseBlock;
import com.aionemu.packetsamurai.parser.formattree.SwitchPart;
import javolution.util.FastList;
/**
*
* @author Gilles Duboscq
*
*/
public class DataStructure
{
private ByteOrder _order;
private ByteBuffer _buf;
private byte[] _unparsed;
private Format _format;
private DataTreeNodeContainer _packetParts;
private boolean _mustUpdate = true;
private boolean _isValid;
protected String _warning;
protected String _error;
private DataPacketMode _mode;
private OrderedDataOutputStream _forgingStream;
public enum DataPacketMode
{
PARSING,
FORGING
};
/**
* Default is Little endian provide a prepared BteBuffer if you want to use an other order
* @param raw
* @param dir
*/
public DataStructure(byte[] raw, Format format)
{
this(ByteBuffer.wrap(raw).order(ByteOrder.LITTLE_ENDIAN), format);
}
public DataStructure(ByteBuffer buf, Format format)
{
this.setByteBuffer(buf);
_format = format;
_mode = DataPacketMode.PARSING;
_order = buf.order();
}
public DataStructure(DataTreeNodeContainer root)
{
this(root,null,ByteOrder.LITTLE_ENDIAN);
}
public DataStructure(DataTreeNodeContainer root, ByteOrder order)
{
this(root,null,order);
}
public DataStructure(DataTreeNodeContainer root, Format format)
{
this(root,format,ByteOrder.LITTLE_ENDIAN);
}
public DataStructure(DataTreeNodeContainer root, Format format, ByteOrder order)
{
if(!root.isRoot())
throw new IllegalArgumentException("The root of a Forging mode packet must be... root :p");
_packetParts = root;
_format = format;
_mode = DataPacketMode.FORGING;
_order = order;
}
public synchronized void parse()
{
if(this.getMode() != DataPacketMode.PARSING)
throw new IllegalStateException("Can not parse a non-parsing mode DataPacket");
if(!_mustUpdate) // could also be used to invalidate parsing results after protocol change
return;
_mustUpdate = false;
_packetParts = new DataTreeNodeContainer();
this.getByteBuffer().rewind();
if (this.getFormat() != null)
{
this.getFormat().registerFormatChangeListener(this);
boolean ret = parse(this.getFormat().getMainBlock(),_packetParts);
this.setValid(ret);
if(!ret &&PacketSamurai.VERBOSITY.ordinal() >= PacketSamurai.VerboseLevel.VERBOSE.ordinal())
{
if (this.getFormat() != null)
{
System.out.println(this.getFormat().toString());
}
dumpParts();
}
}
else
{
this.setValid(false);
}
if(PacketSamurai.VERBOSITY.ordinal() >= PacketSamurai.VerboseLevel.VERY_VERBOSE.ordinal())
{
if(this.getFormat() != null)
{
System.out.println(this.getFormat().toString());
}
dumpParts();
}
}
private boolean parse(PartContainer protocolNode, DataTreeNodeContainer dataNode)
{
for(Part part : protocolNode.getParts())
{
if(part instanceof ForPart)
{
// find the size of this for in the scope
ValuePart vp = dataNode.getPacketValuePartById(((ForPart)part).getForId());
if(vp == null)
{
_error = "Error: could not find valuepart to loop on for (For "+part.getName()+" - id:"+((ForPart)part).getForId()+") in ["+part.getContainingFormat().getContainingPacketFormat()+"]";
return false;
}
if(!(vp instanceof IntValuePart))
{
_error = "Error: for id didnt refer to an IntValePart in (For "+part.getName()+" - id:"+((ForPart)part).getForId()+") in ["+part.getContainingFormat().getContainingPacketFormat()+"]";
return false;
}
int size;
if(vp instanceof IntBCValuePart)
size = ((IntBCValuePart)vp).getBitCount();
else
size = ((IntValuePart)vp).getIntValue();
if(size < 0)
size =- size;
//check size here
if(((ForPart)part).getModelBlock().hasConstantLength())
{
int forBlockSize = ((ForPart)part).getModelBlock().getLength();
if(size*forBlockSize > this.getByteBuffer().remaining())
{
_error = "Error size is too big ("+size+") for For (Part Name: "+part.getName()+" - Id: "+((ForPart)part).getForId()+") in ["+part.getContainingFormat().getContainingPacketFormat()+"]";
return false;
}
}
else if (size > this.getByteBuffer().remaining())
{
_error = "Error size is too big ("+size+") for For (Part Name: "+part.getName()+" - Id: "+((ForPart)part).getForId()+") in ["+part.getContainingFormat().getContainingPacketFormat()+"]";
return false;
}
DataForPart forPart = new DataForPart(dataNode, (ForPart) part);
for(int i = 0; i < size; i++)
{
DataForBlock forBlock = new DataForBlock(forPart, ((ForPart)part).getModelBlock(),i,size);
if(!parse(((ForPart)part).getModelBlock(), forBlock))
return false;
}
}
else if(part instanceof SwitchPart)
{
//find the actual type
ValuePart vp= dataNode.getPacketValuePartById(((SwitchPart)part).getSwitchId());
if (vp == null)
{
_error = "Error: could not find valuepart to switch on for Switch (Part: "+part.getName()+" - id:"+((SwitchPart)part).getSwitchId()+") in ["+part.getContainingFormat().getContainingPacketFormat()+"]";
return false;
}
if (!(vp instanceof IntValuePart))
{
_error = "Error: swicth id didnt refer to an IntValePart in Switch (Part: "+part.getName()+" - id:"+((SwitchPart)part).getSwitchId()+") in ["+part.getContainingFormat().getContainingPacketFormat()+"]";
return false;
}
SwitchCaseBlock caseBlockFormat = ((SwitchPart)part).getCase(((IntValuePart)vp).getIntValue());
if (caseBlockFormat == null)
{
_error = "Error: no such case: "+((IntValuePart)vp).getIntValue()+" for (Switch "+part.getName()+" - id:"+((SwitchPart)part).getSwitchId()+") in ["+part.getContainingFormat().getContainingPacketFormat()+"]";
return false;
}
DataSwitchBlock caseBlock = new DataSwitchBlock(dataNode, caseBlockFormat, vp);
if(!parse(caseBlockFormat,caseBlock))
return false;
}
else if(part instanceof PartContainer)
{
_error = "Error: Unparsed new type of PartContainer ("+this.getClass().getSimpleName()+")";
return false;
}
else if(part.getType().isReadableType())
{
ValuePart vp = part.getType().getValuePart(dataNode, part);
vp.parse(this.getByteBuffer());
}
}
return true;
}
public synchronized boolean forge()
{
if(this.getMode() != DataPacketMode.FORGING)
throw new IllegalStateException("Can not forge a non-forging mode DataPacket");
if(!isTreeValid())
throw new IllegalStateException("Tree must be valid before Forging");
ByteArrayOutputStream forgingBaos = new ByteArrayOutputStream();
_forgingStream = new OrderedDataOutputStream(forgingBaos,_order);
boolean ret = this.forge(this.getRootNode());
ByteBuffer newbuf = ByteBuffer.allocate(forgingBaos.size());
newbuf.put(forgingBaos.toByteArray());
newbuf.rewind();
newbuf.order(_order);
this.setByteBuffer(newbuf);
return ret;
}
private boolean forge(DataTreeNodeContainer node)
{
for(DataTreeNode subnode : node.getNodes())
{
if(subnode instanceof DataForPart)
{
for(DataForBlock block :((DataForPart)subnode).getNodes())
{
forge(block);
}
}
else if(subnode instanceof DataSwitchBlock)
{
forge((DataSwitchBlock) subnode);
}
else if(subnode instanceof ValuePart)
{
try
{
((ValuePart)subnode).forge(_forgingStream);
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return true;
}
public DataTreeNodeContainer getRootNode()
{
if(this.getMode() == DataPacketMode.PARSING)
this.parse();
return _packetParts;
}
public DataPacketMode getMode()
{
return _mode;
}
public Format getFormat()
{
return _format;
}
public void setFormat(Format f)
{
_format = f;
}
public void dumpParts()
{
dumpParts(_packetParts, 0);
}
public void dumpParts(DataTreeNodeContainer node, int depth)
{
for (DataTreeNode n : node.getNodes())
{
if(n instanceof ValuePart)
{
System.out.println(Util.repeat(" ", depth*2)+"Name: "+((ValuePart)n).getModelPart().getName()+" ("+((ValuePart)n).getType()+") - Part value: "+((ValuePart)n).getValueAsString()+" "+((ValuePart)n).readValue());
}
else
{
System.out.println(Util.repeat(" ", depth*2)+"Name: "+((DataTreeNodeContainer)n).getModelPart().getName()+" - Part: "+((DataTreeNodeContainer)n).getModelPart().getType());
dumpParts((DataTreeNodeContainer)n, depth+1);
}
}
}
// All those read methods shouldnt exist
public int readC()
{
return getByteBuffer().get() & 0xFF;
}
public int readH()
{
return getByteBuffer().getShort() & 0xFFFF;
}
public int readD()
{
return getByteBuffer().getInt();
}
public double readF()
{
return getByteBuffer().getDouble();
}
public String readS()
{
StringBuffer sb = new StringBuffer();
char ch;
while ((ch = getByteBuffer().getChar()) != 0)
sb.append(ch);
return sb.toString();
}
public byte[] readB( int length)
{
byte[] result = new byte[length];
getByteBuffer().get(result);
return result;
}
public int readIntType(PartType type)
{
switch (type.getTypeByteNumber())
{
case 1:
return readC();
case 2:
return readH();
case 4:
return readD();
}
throw new IllegalArgumentException("Type is not an Int :"+type.getName());
}
public byte get(int idx)
{
return getByteBuffer().get(idx);
}
public byte[] getUnparsedData()
{
if (_unparsed == null)
{
int size = getByteBuffer().remaining();
_unparsed = new byte[size];
getByteBuffer().get(_unparsed);
}
return _unparsed;
}
public byte[] getData()
{
return getByteBuffer().array();
}
public int getSize()
{
return getByteBuffer().limit();
}
/**
* to be used by UI
* @return
*/
public List<ValuePart> getValuePartList()
{
return getValuePartList(this.getRootNode());
}
private List<ValuePart> getValuePartList(DataTreeNodeContainer node)
{
FastList<ValuePart> parts = new FastList<ValuePart>();
for(DataTreeNode n : node.getNodes())
{
if(n instanceof ValuePart)
parts.add((ValuePart) n);
else if(n instanceof DataTreeNodeContainer)
parts.addAll(getValuePartList((DataTreeNodeContainer) n));
}
return parts;
}
protected void setValid(boolean val)
{
_isValid = val;
}
public boolean isValid()
{
return _isValid || _mustUpdate;
}
public void invalidateParsing()
{
if(this.getMode() != DataPacketMode.PARSING)
throw new IllegalStateException("Can not invalidate parsing on a non-parsing mode DataPacket");
synchronized(this)
{
this.setValid(false);
_mustUpdate = true;
_packetParts = null;
}
}
public void invalidateForging()
{
if(this.getMode() != DataPacketMode.FORGING)
throw new IllegalStateException("Can not invalidate forging on a non-forging mode DataPacket");
synchronized(this)
{
this.setValid(false);
_mustUpdate = true;
}
}
public boolean hasWarning()
{
return _warning != null;
}
public boolean hasError()
{
return _error != null;
}
public String getErrorMessage()
{
return _error != null ? _error : _warning;
}
public boolean isTreeValid()
{
boolean ret;
if(this.getFormat() != null)
{
// validate against Format
ret = validateTree(this.getRootNode(), this.getFormat().getMainBlock());
}
else
{
// just validate the structure
ret = validateTree(this.getRootNode());
}
return ret;
}
public ByteBuffer getByteBuffer()
{
return _buf;
}
protected void setByteBuffer(ByteBuffer buf)
{
//if(this.getMode() == DataPacketMode.FORGING)
// throw new IllegalStateException("Can not force the ByteBuffer for a Forging mode DataPacket");
_buf = buf;
}
private boolean validateTree(DataTreeNodeContainer node)
{
boolean insideFor = node instanceof DataForPart;
DataForBlock model = null;
for(DataTreeNode n : node.getNodes())
{
if(n instanceof DataForPart)
{
if(insideFor)
return false;
if(!validateTree((DataForPart)n))
return false;
}
else if(n instanceof DataSwitchBlock)
{
if(insideFor)
return false;
if(!validateTree((DataSwitchBlock)n))
return false;
}
else if(n instanceof DataForBlock)
{
if(!insideFor)
return false;
if(model == null)
{
model = (DataForBlock) n;
}
else
{
if(!branchesEqual(model, (DataForBlock)n))
return false;
}
if(!validateTree((DataForBlock)n))
return false;
}
else if(insideFor)
{
return false;
}
}
return true;
}
private boolean branchesEqual(DataTreeNodeContainer branch1, DataTreeNodeContainer branch2)
{
Iterator<? extends DataTreeNode> it1 = branch1.getNodes().iterator();
Iterator<? extends DataTreeNode> it2 = branch2.getNodes().iterator();
while(it1.hasNext())
{
if(!it2.hasNext())
return false;
DataTreeNode node1 = it1.next();
DataTreeNode node2 = it2.next();
if(node1.getClass() != node2.getClass())
return false;
if(node1 instanceof IntValuePart && ((IntValuePart)node1).getType() != ((IntValuePart)node2).getType())
return false;
//if(node1 instanceof D)
}
if(it2.hasNext())
return false;
return true;
}
private boolean validateTree(DataTreeNodeContainer dataTreeNode, PartContainer protocolNode)
{
return true;
}
}