Obviously, I wouldn't be telling you all of this, if Preon would not be able to do that. So let me show you how. The full code is listed below.
The important bits are in line 60.
The code that we used to have there was this:
@Slice(size="(packetRecordLength - 24) * 8")
@BoundList(size="includedLength")
private byte[] packetData;
In the new version of SnoopFile, that changed into:
@Slice(size = "(packetRecordLength - 24) * 8")
@BoundObject(selectFrom = @Choices(alternatives =
@Choice(condition = "outer.header.datalinkType==DatalinkType.ETHERNET", type = EthernetFrame.class))
)
private Object packetData;
Basically, instead of decoding the packet data into a byte array, it's now immediately turned into an object. An object that actually has the relevant data already decoded. Now, don't be alarmed by the type of packetData. In the new code it's declared to be of type java.lang.Object. That's just to make sure that it will be able to cover for all of the types of packet data that you could possibly encounter. In the common case, it will be ethernet frames. But Snoop is able to capture data for other data link types as well, and it's fairly unlikely that these different types of packet data would have commonalities to be captured in a base class.
So, even thought he type of packetData is java.lang.Object, Preon will actually decode packet data in objects of a particular type, based on the data link type. The selectFrom attribute on @BoundObject contains the rules for picking the appropriate type of object. Currently, it only covers for Ethernet packages. It basically reads: if the data link type read as part of the header is Ethernet, then decode the data into an instance of EthernetFrame.
The EthernetFrame itself is defined like this:
public static class EthernetFrame {
@BoundList(size = "6")
private byte[] destinationAddress;
@BoundList(size = "6")
private byte[] sourceAddress;
@BoundNumber(size = "16")
private int type;
@BoundList(size="outer.includedLength - (6 + 6 + 2)")
private byte[] data;
}
Not much to explain here. It's pretty much straightforward Preon code. Note that the number of bytes is calculated based on an attribute of the packet record. Since the EthernetFrame is read as part of the PacketRecord, you *can* actually refer to the 'outer' context, and reference attributes of that outer context.
public class SnoopFile {
@Bound
private FileHeader header;
@BoundList(type = PacketRecord.class)
private List<PacketRecord> records;
public FileHeader getHeader() {
return header;
}
public List<PacketRecord> getRecords() {
return records;
}
public static class FileHeader {
@BoundBuffer(match = { 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00, 0x00, 0x00 })
private byte[] identificationPattern;
@BoundNumber(byteOrder = ByteOrder.BigEndian)
private int versionNumber;
@BoundNumber(size = "32", byteOrder = ByteOrder.BigEndian)
private DatalinkType datalinkType;
public int getVersionNumber() {
return versionNumber;
}
public DatalinkType getDatalinkType() {
return datalinkType;
}
}
@ImportStatic(DatalinkType.class)
public static class PacketRecord {
@BoundNumber(byteOrder = ByteOrder.BigEndian, size = "32")
private long originalLength;
@BoundNumber(byteOrder = ByteOrder.BigEndian, size = "32")
private long includedLength;
@BoundNumber(byteOrder = ByteOrder.BigEndian, size = "32")
private long packetRecordLength;
@BoundNumber(byteOrder = ByteOrder.BigEndian, size = "32")
private long cumulativeDrops;
@BoundNumber(byteOrder = ByteOrder.BigEndian, size = "32")
private long timestampSeconds;
@BoundNumber(byteOrder = ByteOrder.BigEndian, size = "32")
private long timestampMicroseconds;
@Slice(size = "(packetRecordLength - 24) * 8")
@BoundObject(selectFrom = @Choices(alternatives = @Choice(condition = "outer.header.datalinkType==DatalinkType.ETHERNET", type = EthernetFrame.class)))
private Object packetData;
public long getOriginalLength() {
return originalLength;
}
public long getIncludedLength() {
return includedLength;
}
public long getPacketRecordLength() {
return packetRecordLength;
}
public long getCumulativeDrops() {
return cumulativeDrops;
}
public long getTimestampSeconds() {
return timestampSeconds;
}
public long getTimestampMicroseconds() {
return timestampMicroseconds;
}
public Object getPacketData() {
return packetData;
}
public static class EthernetFrame {
@BoundList(size = "6")
private byte[] destinationAddress;
@BoundList(size = "6")
private byte[] sourceAddress;
@BoundNumber(size = "16")
private int type;
@BoundList(size="outer.includedLength - (6 + 6 + 2)")
private byte[] data;
public String getDestinationAddress() {
return render(destinationAddress);
}
public String getSourceAddress() {
return render(sourceAddress);
}
private String render(byte[] address) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < address.length; i++) {
if (i != 0) {
builder.append(':');
}
builder.append(Integer.toHexString(0xff&address[i]));
}
return builder.toString();
}
}
}
public static enum DatalinkType {
@BoundEnumOption(0)
IEEE_802_3,
@BoundEnumOption(1)
IEEE_802_4_TOKEN_BUS,
@BoundEnumOption(2)
IEEE_802_5_TOKEN_RING,
@BoundEnumOption(3)
IEEE_802_6_METRO_NET,
@BoundEnumOption(4)
ETHERNET,
@BoundEnumOption(5)
HLDC,
@BoundEnumOption(6)
CHARACTER_SYNCHRONOUS,
@BoundEnumOption(7)
IBM_CHANNEL_TO_CHANNEL,
@BoundEnumOption(8)
FDDI,
@BoundEnumOption(9)
OTHER,
UNASSIGNED
}
}
0 comments:
Post a Comment