/******************************************************************************* * Copyright (c) 2014 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Vincent Perot - Initial API and implementation *******************************************************************************/ package org.eclipse.tracecompass.internal.pcap.core.trace; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.util.TreeMap; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.internal.pcap.core.packet.BadPacketException; import org.eclipse.tracecompass.internal.pcap.core.protocol.pcap.PcapPacket; import org.eclipse.tracecompass.internal.pcap.core.util.ConversionHelper; import org.eclipse.tracecompass.internal.pcap.core.util.PcapTimestampScale; /** * Class that allows the interaction with a pcap file. * * @author Vincent Perot */ public class PcapFile implements Closeable { // TODO add pcapng support. // TODO Make parsing faster by buffering the data. private final Path fPcapFilePath; private final ByteOrder fByteOrder; private final SeekableByteChannel fFileChannel; private final PcapTimestampScale fTimestampPrecision; private final int fMajorVersion; private final int fMinorVersion; private final long fTimeAccuracy; private final long fTimeZoneCorrection; private final long fSnapshotLength; private final long fDataLinkType; private final TreeMap<Long, Long> fFileIndex; private long fCurrentRank; private long fTotalNumberPackets; /** * Constructor of the PcapFile Class. * * @param filePath * The path to the pcap file. * * @throws BadPcapFileException * Thrown if the Pcap File is not valid. * @throws IOException * Thrown if there is an IO error while reading the file. */ public PcapFile(Path filePath) throws BadPcapFileException, IOException { fFileIndex = new TreeMap<>(); fCurrentRank = 0; fTotalNumberPackets = -1; fPcapFilePath = filePath; // Check file validity if (Files.notExists(fPcapFilePath) || !Files.isRegularFile(fPcapFilePath) || Files.size(fPcapFilePath) < PcapFileValues.GLOBAL_HEADER_SIZE) { throw new BadPcapFileException("Bad Pcap File."); //$NON-NLS-1$ } if (!Files.isReadable(fPcapFilePath)) { throw new BadPcapFileException("File is not readable."); //$NON-NLS-1$ } // File is not empty. Try to open. fFileChannel = checkNotNull(Files.newByteChannel(fPcapFilePath)); // Parse the global header. // Read the magic number (4 bytes) from the input stream // and determine the mode (big endian or little endian) ByteBuffer globalHeader = ByteBuffer.allocate(PcapFileValues.GLOBAL_HEADER_SIZE); globalHeader.clear(); fFileChannel.read(globalHeader); globalHeader.flip(); int magicNumber = globalHeader.getInt(); switch (magicNumber) { case PcapFileValues.MAGIC_BIG_ENDIAN_MICRO: // file is big endian fByteOrder = ByteOrder.BIG_ENDIAN; fTimestampPrecision = PcapTimestampScale.MICROSECOND; break; case PcapFileValues.MAGIC_LITTLE_ENDIAN_MICRO: // file is little endian fByteOrder = ByteOrder.LITTLE_ENDIAN; fTimestampPrecision = PcapTimestampScale.MICROSECOND; break; case PcapFileValues.MAGIC_BIG_ENDIAN_NANO: // file is big endian fByteOrder = ByteOrder.BIG_ENDIAN; fTimestampPrecision = PcapTimestampScale.NANOSECOND; break; case PcapFileValues.MAGIC_LITTLE_ENDIAN_NANO: // file is little endian fByteOrder = ByteOrder.LITTLE_ENDIAN; fTimestampPrecision = PcapTimestampScale.NANOSECOND; break; default: this.close(); throw new BadPcapFileException(String.format("%08x", magicNumber) + " is not a known magic number."); //$NON-NLS-1$ //$NON-NLS-2$ } // Put the rest of the buffer in file endian. globalHeader.order(fByteOrder); // Initialization of global header fields. fMajorVersion = ConversionHelper.unsignedShortToInt(globalHeader.getShort()); fMinorVersion = ConversionHelper.unsignedShortToInt(globalHeader.getShort()); fTimeAccuracy = ConversionHelper.unsignedIntToLong(globalHeader.getInt()); fTimeZoneCorrection = ConversionHelper.unsignedIntToLong(globalHeader.getInt()); fSnapshotLength = ConversionHelper.unsignedIntToLong(globalHeader.getInt()); fDataLinkType = ConversionHelper.unsignedIntToLong(globalHeader.getInt()); fFileIndex.put(fCurrentRank, fFileChannel.position()); } /** * Method that allows the parsing of a packet at the current position. * * @return The parsed Pcap Packet. * @throws IOException * Thrown when there is an error while reading the file. * @throws BadPcapFileException * Thrown when a packet header is invalid. * @throws BadPacketException * Thrown when the packet is erroneous. */ public synchronized @Nullable PcapPacket parseNextPacket() throws IOException, BadPcapFileException, BadPacketException { // Parse the packet header if (fFileChannel.size() - fFileChannel.position() == 0) { return null; } if (fFileChannel.size() - fFileChannel.position() < PcapFileValues.PACKET_HEADER_SIZE) { throw new BadPcapFileException("A pcap header is invalid."); //$NON-NLS-1$ } ByteBuffer pcapPacketHeader = ByteBuffer.allocate(PcapFileValues.PACKET_HEADER_SIZE); pcapPacketHeader.clear(); pcapPacketHeader.order(fByteOrder); fFileChannel.read(pcapPacketHeader); pcapPacketHeader.flip(); pcapPacketHeader.position(PcapFileValues.INCLUDED_LENGTH_POSITION); long includedPacketLength = ConversionHelper.unsignedIntToLong(pcapPacketHeader.getInt()); if (fFileChannel.size() - fFileChannel.position() < includedPacketLength) { throw new BadPcapFileException("A packet header is invalid."); //$NON-NLS-1$ } if (includedPacketLength > Integer.MAX_VALUE) { throw new BadPacketException("Packets that are bigger than 2^31-1 bytes are not supported."); //$NON-NLS-1$ } ByteBuffer pcapPacketData = ByteBuffer.allocate((int) includedPacketLength); pcapPacketData.clear(); pcapPacketHeader.order(ByteOrder.BIG_ENDIAN); // Not really needed. fFileChannel.read(pcapPacketData); pcapPacketData.flip(); fFileIndex.put(++fCurrentRank, fFileChannel.position()); return new PcapPacket(this, null, pcapPacketHeader, pcapPacketData, fCurrentRank - 1); } /** * Method that allows to skip a packet at the current position. * * @throws IOException * Thrown when there is an error while reading the file. * @throws BadPcapFileException * Thrown when a packet header is invalid. */ public synchronized void skipNextPacket() throws IOException, BadPcapFileException { // Parse the packet header if (fFileChannel.size() - fFileChannel.position() == 0) { return; } if (fFileChannel.size() - fFileChannel.position() < PcapFileValues.PACKET_HEADER_SIZE) { throw new BadPcapFileException("A pcap header is invalid."); //$NON-NLS-1$ } ByteBuffer pcapPacketHeader = ByteBuffer.allocate(PcapFileValues.PACKET_HEADER_SIZE); pcapPacketHeader.clear(); pcapPacketHeader.order(fByteOrder); fFileChannel.read(pcapPacketHeader); pcapPacketHeader.flip(); pcapPacketHeader.position(PcapFileValues.INCLUDED_LENGTH_POSITION); long includedPacketLength = ConversionHelper.unsignedIntToLong(pcapPacketHeader.getInt()); if (fFileChannel.size() - fFileChannel.position() < includedPacketLength) { throw new BadPcapFileException("A packet header is invalid."); //$NON-NLS-1$ } fFileChannel.position(fFileChannel.position() + includedPacketLength); fFileIndex.put(++fCurrentRank, fFileChannel.position()); } /** * Method that moves the position to the specified rank. * * @param rank * The rank of the packet. * * @throws IOException * Thrown when there is an error while reading the file. * @throws BadPcapFileException * Thrown when a packet header is invalid. */ public synchronized void seekPacket(long rank) throws IOException, BadPcapFileException { // Verify argument if (rank < 0) { throw new IllegalArgumentException(); } Long positionInBytes = fFileIndex.get(rank); if (positionInBytes != null) { // Index is known. Move to position. fFileChannel.position(positionInBytes.longValue()); fCurrentRank = rank; } else { // Index is unknown. Find the corresponding position. // Find closest index fCurrentRank = fFileIndex.floorKey(rank); // skip until wanted packet is found do { skipNextPacket(); } while (fCurrentRank != rank && hasNextPacket()); } } /** * Method that indicates if there are packets remaining to read. It is an * end of file indicator. * * @return Whether the pcap still has packets or not. * @throws IOException * If some IO error occurs. */ public synchronized boolean hasNextPacket() throws IOException { return ((fFileChannel.size() - fFileChannel.position()) > 0); } /** * Getter method for the Byte Order of the file. * * @return The byte Order of the file. */ public ByteOrder getByteOrder() { return fByteOrder; } /** * Getter method for the Major Version of the file. * * @return The Major Version of the file. */ public int getMajorVersion() { return fMajorVersion; } /** * Getter method for the Minor Version of the file. * * @return The Minor Version of the file. */ public int getMinorVersion() { return fMinorVersion; } /** * Getter method for the time accuracy of the file. * * @return The time accuracy of the file. */ public long getTimeAccuracy() { return fTimeAccuracy; } /** * Getter method for the time zone correction of the file. * * @return The time zone correction of the file. */ public long getTimeZoneCorrection() { return fTimeZoneCorrection; } /** * Getter method for the snapshot length of the file. * * @return The snapshot length of the file. */ public long getSnapLength() { return fSnapshotLength; } /** * Getter method for the datalink type of the file. This parameter is used * to determine higher-level protocols (Ethernet, WLAN, SLL). * * @return The datalink type of the file. */ public long getDataLinkType() { return fDataLinkType; } /** * Getter method for the path of the file. * * @return The path of the file. */ public Path getPath() { return fPcapFilePath; } /** * Method that returns the total number of packets in the file. * * @return The total number of packets. * @throws IOException * Thrown when some IO error occurs. * @throws BadPcapFileException * Thrown when a packet header is invalid. */ public synchronized long getTotalNbPackets() throws IOException, BadPcapFileException { if (fTotalNumberPackets == -1) { long rank = fCurrentRank; fCurrentRank = fFileIndex.floorKey(rank); // skip until end of file. while (hasNextPacket()) { skipNextPacket(); } fTotalNumberPackets = fCurrentRank; fCurrentRank = rank; seekPacket(rank); } return fTotalNumberPackets; } /** * Getter method that returns the current rank in the file (the packet * number). * * @return The current rank. */ public synchronized long getCurrentRank() { return fCurrentRank; } /** * Getter method that returns the timestamp precision of the file. * * @return The the timestamp precision of the file. */ public PcapTimestampScale getTimestampPrecision() { return fTimestampPrecision; } @Override public void close() throws IOException { fFileChannel.close(); } }