/**
 * Copyright (c) 2005, Regents of the University of California
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the University of California, Los Angeles nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package cck.elf;

import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * The <code>ELFHeader</code> class represents the header of an ELF file.
 * It can load the header from a file and check the identification, identify
 * the version, endianness, and detect which architecture the file
 * has been created for.
 *
 * @author Ben L. Titzer
 */
public class ELFHeader {

    protected static final int ELFCLASSNONE = 0;
    protected static final int ELFCLASS32   = 1;
    protected static final int ELFCLASS64   = 2;
    protected static final int ELFCLASSNUM  = 3;

    protected static final int EI_NIDENT = 16;

    // constants for information within e_ident section
    protected static final int EI_CLASS = 4;
    protected static final int EI_DATA = 5;
    protected static final int EI_VERSION = 6;
    protected static final int EI_PAD = 7;

    // constants for data format
    protected static final int ELFDATA2LSB = 1;
    protected static final int ELFDATA2MSB = 2;

    public final byte[] e_ident;
    public short e_type;
    public short e_machine;
    public int e_version;
    public int e_entry;
    public int e_phoff;
    public int e_shoff;
    public int e_flags;
    public short e_ehsize;
    public short e_phentsize;
    public short e_phnum;
    public short e_shentsize;
    public short e_shnum;
    public short e_shstrndx;

    boolean bigEndian;

    public class FormatError extends Exception {

        private static final long serialVersionUID = 1L;

    }

    /**
     * The default constructor for the <code>ELFHeader</code> class simply creates a new, unitialized
     * instance of this class that is ready to load.
     */
    public ELFHeader() {
        e_ident = new byte[EI_NIDENT];
    }

    /**
     * The <code>read()</code> method reads the header from the specified input stream.
     * It loads the identification section and checks that the header is present by testing against
     * the magic ELF values, and reads the rests of the data section, initializes the ELF section.
     * @param fs the input stream from which to read the ELF header
     * @throws IOException if there is a problem reading from the input stream
     */
    public void read(RandomAccessFile fs) throws IOException, FormatError {
        // read the indentification string
        if ( fs.length() < EI_NIDENT ) throw new FormatError();
        for ( int index = 0; index < EI_NIDENT; )
            index += fs.read(e_ident, index, EI_NIDENT - index);
        checkIdent();
        ELFDataInputStream is = new ELFDataInputStream(this, fs);
        e_type      = is.read_Elf32_Half();
        e_machine   = is.read_Elf32_Half();
        e_version   = is.read_Elf32_Word();
        e_entry     = is.read_Elf32_Addr();
        e_phoff     = is.read_Elf32_Off();
        e_shoff     = is.read_Elf32_Off();
        e_flags     = is.read_Elf32_Word();
        e_ehsize    = is.read_Elf32_Half();
        e_phentsize = is.read_Elf32_Half();
        e_phnum     = is.read_Elf32_Half();
        e_shentsize = is.read_Elf32_Half();
        e_shnum     = is.read_Elf32_Half();
        e_shstrndx  = is.read_Elf32_Half();
    }

    private void checkIdent() throws FormatError {
        checkIndentByte(0, 0x7f);
        checkIndentByte(1, 'E');
        checkIndentByte(2, 'L');
        checkIndentByte(3, 'F');
        bigEndian = isBigEndian();
    }

    private void checkIndentByte(int ind, int val) throws FormatError {
        if ( e_ident[ind] != val ) throw new FormatError();
    }

    /**
     * The <code>getVersion()</code> method returns the version of this ELF file. The version
     * number is stored in the identification section of the ELF file at the beginning.
     * @return the version of this ELF file as an integer
     */
    public int getVersion() {
        return e_ident[EI_VERSION];
    }

    /**
     * The <code>getArchitecture()</code> method resolves the name of the architecture
     * from the <code>e_machine</code> file of the structure, using an internal map for
     * well-known architectures.
     * @return a String representation of the architecture name
     */
    public String getArchitecture() {
        return ELFIdentifier.getArchitecture(e_machine);
    }

    /**
     * The <code>isLittleEndian()</code> method checks whether this ELF file is encoded
     * in the little endian format (i.e. least signficant byte first). This information is
     * present in the identification section of the ELF file
     * @return true if this file has the little endian data format; false otherwise
     */
    public boolean isLittleEndian() {
        return e_ident[EI_VERSION] == ELFDATA2LSB;
    }

    /**
     * The <code>isBigEndian()</code> method checks whether this ELF file is encoded
     * in big endian format (i.e. most signficant byte first). This information is present
     * in the identification section of the ELF file.
     * @return true if this file has the big endian data format; false otherwise
     */
    public boolean isBigEndian() {
        return e_ident[EI_VERSION] == ELFDATA2MSB;
    }

    /**
     * The <code>is32Bit()</code> method checks whether this ELF file is encoded
     * as 32 or 64 bits. This information is contained in the header in the EI_CLASS
     * byte.
     * @return true if this ELF file is 32 bit
     */
    public boolean is32Bit()  {
        return e_ident[EI_CLASS] == ELFCLASS32;
    }

    /**
     * The <code>is64Bit()</code> method checks whether this ELF file is encoded
     * as 32 or 64 bits. This information is contained in the header in the EI_CLASS
     * byte.
     * @return true if this ELF file is 64 bit
     */
    public boolean is64Bit()  {
        return e_ident[EI_CLASS] == ELFCLASS64;
    }
}
