/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.processors.evtx.parser;
import com.google.common.primitives.UnsignedInteger;
import com.google.common.primitives.UnsignedLong;
import org.apache.nifi.logging.ComponentLog;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.CRC32;
/**
* FileHeader at the top of an Evtx file has metadata about the chunks of the file as well as top-level metadata
*/
public class FileHeader extends Block {
public static final int CHUNK_SIZE = 65536;
public static final String ELF_FILE = "ElfFile";
private final String magicString;
private final UnsignedLong oldestChunk;
private final UnsignedLong currentChunkNumber;
private final UnsignedLong nextRecordNumber;
private final UnsignedInteger headerSize;
private final int minorVersion;
private final int majorVersion;
private final int headerChunkSize;
private final int chunkCount;
private final String unused1;
private final UnsignedInteger flags;
private final UnsignedInteger checksum;
private final InputStream inputStream;
private final ComponentLog log;
private long currentOffset;
private int count = 1;
public FileHeader(InputStream inputStream, ComponentLog log) throws IOException {
super(new BinaryReader(inputStream, 4096));
this.log = log;
// Bytes will be checksummed
BinaryReader binaryReader = getBinaryReader();
CRC32 crc32 = new CRC32();
crc32.update(binaryReader.peekBytes(120));
magicString = binaryReader.readString(8);
if (!ELF_FILE.equals(magicString)) {
throw new IOException("Invalid magic string. Expected " + ELF_FILE + " got " + magicString);
}
oldestChunk = binaryReader.readQWord();
currentChunkNumber = binaryReader.readQWord();
nextRecordNumber = binaryReader.readQWord();
headerSize = binaryReader.readDWord();
minorVersion = binaryReader.readWord();
majorVersion = binaryReader.readWord();
headerChunkSize = binaryReader.readWord();
chunkCount = binaryReader.readWord();
unused1 = binaryReader.readString(76);
// Not part of checksum
flags = binaryReader.readDWord();
checksum = binaryReader.readDWord();
if (crc32.getValue() != checksum.longValue()) {
throw new IOException("Invalid checksum");
}
NumberUtil.intValueExpected(minorVersion, 1, "Invalid minor version.");
NumberUtil.intValueExpected(majorVersion, 3, "Invalid minor version.");
NumberUtil.intValueExpected(headerChunkSize, 4096, "Invalid header chunk size.");
this.inputStream = inputStream;
currentOffset = 4096;
init();
}
@Override
protected int getHeaderLength() {
return 4096;
}
public String getMagicString() {
return magicString;
}
public UnsignedLong getOldestChunk() {
return oldestChunk;
}
public UnsignedLong getCurrentChunkNumber() {
return currentChunkNumber;
}
public UnsignedLong getNextRecordNumber() {
return nextRecordNumber;
}
public UnsignedInteger getHeaderSize() {
return headerSize;
}
public int getMinorVersion() {
return minorVersion;
}
public int getMajorVersion() {
return majorVersion;
}
public int getHeaderChunkSize() {
return headerChunkSize;
}
public int getChunkCount() {
return chunkCount;
}
public String getUnused1() {
return unused1;
}
public UnsignedInteger getFlags() {
return flags;
}
public UnsignedInteger getChecksum() {
return checksum;
}
/**
* Tests whether there are more chunks
* @return true iff there are chunks left
*/
public boolean hasNext() {
return count < chunkCount;
}
/**
* Returns the next chunkHeader or null if there are no more
*
* @return chunkHeader
* @throws MalformedChunkException if there is an error reading the chunk header
* @throws IOException if there is an exception creating the BinaryReader
*/
public ChunkHeader next() throws MalformedChunkException, IOException {
if (count <= chunkCount) {
long currentOffset = this.currentOffset;
this.currentOffset += CHUNK_SIZE;
BinaryReader binaryReader = new BinaryReader(inputStream, CHUNK_SIZE);
try {
return new ChunkHeader(binaryReader, log, currentOffset, count++);
} catch (IOException e) {
throw new MalformedChunkException("Malformed chunk, unable to parse", e, currentOffset, count - 1, binaryReader.getBytes());
}
} else {
return null;
}
}
}