/* * 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.annotations.VisibleForTesting; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; import org.apache.nifi.logging.ComponentLog; import org.apache.nifi.processors.evtx.parser.bxml.NameStringNode; import org.apache.nifi.processors.evtx.parser.bxml.TemplateNode; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.zip.CRC32; /** * A Chunk is a self-contained group of templates, strings, and nodes */ public class ChunkHeader extends Block { public static final String ELF_CHNK = "ElfChnk"; private final String magicString; private final UnsignedLong fileFirstRecordNumber; private final UnsignedLong fileLastRecordNumber; private final UnsignedLong logFirstRecordNumber; private final UnsignedLong logLastRecordNumber; private final UnsignedInteger headerSize; private final UnsignedInteger lastRecordOffset; private final int nextRecordOffset; private final UnsignedInteger dataChecksum; private final String unused; private final UnsignedInteger headerChecksum; private final Map<Integer, NameStringNode> nameStrings; private final Map<Integer, TemplateNode> templateNodes; private final int chunkNumber; private final ComponentLog log; private UnsignedLong recordNumber; public ChunkHeader(BinaryReader binaryReader, ComponentLog log, long headerOffset, int chunkNumber) throws IOException { super(binaryReader, headerOffset); this.log = log; this.chunkNumber = chunkNumber; CRC32 crc32 = new CRC32(); crc32.update(binaryReader.peekBytes(120)); magicString = binaryReader.readString(8); fileFirstRecordNumber = binaryReader.readQWord(); fileLastRecordNumber = binaryReader.readQWord(); logFirstRecordNumber = binaryReader.readQWord(); logLastRecordNumber = binaryReader.readQWord(); headerSize = binaryReader.readDWord(); lastRecordOffset = binaryReader.readDWord(); nextRecordOffset = NumberUtil.intValueMax(binaryReader.readDWord(), Integer.MAX_VALUE, "Invalid next record offset."); dataChecksum = binaryReader.readDWord(); unused = binaryReader.readString(68); if (!ELF_CHNK.equals(magicString)) { throw new IOException("Invalid magic string " + this); } headerChecksum = binaryReader.readDWord(); // These are included into the checksum crc32.update(binaryReader.peekBytes(384)); if (crc32.getValue() != headerChecksum.longValue()) { throw new IOException("Invalid checksum " + this); } if (lastRecordOffset.compareTo(UnsignedInteger.valueOf(Integer.MAX_VALUE)) > 0) { throw new IOException("Last record offset too big to fit into signed integer"); } nameStrings = new HashMap<>(); for (int i = 0; i < 64; i++) { int offset = NumberUtil.intValueMax(binaryReader.readDWord(), Integer.MAX_VALUE, "Invalid offset."); while (offset > 0) { NameStringNode nameStringNode = new NameStringNode(new BinaryReader(binaryReader, offset), this); nameStrings.put(offset, nameStringNode); offset = NumberUtil.intValueMax(nameStringNode.getNextOffset(), Integer.MAX_VALUE, "Invalid offset."); } } templateNodes = new HashMap<>(); for (int i = 0; i < 32; i++) { int offset = NumberUtil.intValueMax(binaryReader.readDWord(), Integer.MAX_VALUE, "Invalid offset."); while (offset > 0) { int token = new BinaryReader(binaryReader, offset - 10).read(); if (token != 0x0c) { log.warn("Unexpected token when parsing template at offset " + offset); break; } BinaryReader templateReader = new BinaryReader(binaryReader, offset - 4); int pointer = NumberUtil.intValueMax(templateReader.readDWord(), Integer.MAX_VALUE, "Invalid pointer."); if (offset != pointer) { log.warn("Invalid pointer when parsing template at offset " + offset); break; } TemplateNode templateNode = new TemplateNode(templateReader, this); templateNodes.put(offset, templateNode); offset = templateNode.getNextOffset(); } } crc32 = new CRC32(); crc32.update(binaryReader.peekBytes(nextRecordOffset - 512)); if (crc32.getValue() != dataChecksum.longValue()) { throw new IOException("Invalid data checksum " + this); } recordNumber = fileFirstRecordNumber.minus(UnsignedLong.ONE); } public NameStringNode addNameStringNode(int offset, BinaryReader binaryReader) throws IOException { NameStringNode nameStringNode = new NameStringNode(binaryReader, this); nameStrings.put(offset, nameStringNode); return nameStringNode; } public TemplateNode addTemplateNode(int offset, BinaryReader binaryReader) throws IOException { TemplateNode templateNode = new TemplateNode(binaryReader, this); templateNodes.put(offset, templateNode); return templateNode; } public TemplateNode getTemplateNode(int offset) { return templateNodes.get(offset); } @Override public String toString() { return "ChunkHeader{" + "magicString='" + magicString + '\'' + ", fileFirstRecordNumber=" + fileFirstRecordNumber + ", fileLastRecordNumber=" + fileLastRecordNumber + ", logFirstRecordNumber=" + logFirstRecordNumber + ", logLastRecordNumber=" + logLastRecordNumber + ", headerSize=" + headerSize + ", lastRecordOffset=" + lastRecordOffset + ", nextRecordOffset=" + nextRecordOffset + ", dataChecksum=" + dataChecksum + ", unused='" + unused + '\'' + ", headerChecksum=" + headerChecksum + '}'; } public boolean hasNext() { return fileLastRecordNumber.compareTo(recordNumber) > 0; } public String getString(int offset) { NameStringNode nameStringNode = nameStrings.get(offset); if (nameStringNode == null) { return null; } return nameStringNode.getString(); } @VisibleForTesting Map<Integer, NameStringNode> getNameStrings() { return Collections.unmodifiableMap(nameStrings); } @VisibleForTesting Map<Integer, TemplateNode> getTemplateNodes() { return Collections.unmodifiableMap(templateNodes); } public int getChunkNumber() { return chunkNumber; } public Record next() throws IOException { if (!hasNext()) { return null; } try { Record record = new Record(getBinaryReader(), this); recordNumber = record.getRecordNum(); return record; } catch (IOException e) { recordNumber = fileLastRecordNumber; throw e; } } }