
package com.sun.jna;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

/**
 * Analyse an ELF file for platform specific attributes.
 * 
 * <p>Primary use-case: Detect whether the java binary is arm hardfloat or softfloat.</p>
 */
class ELFAnalyser {
    /**
     * Generic ELF header
     */
    private static final byte[] ELF_MAGIC = new byte[]{(byte) 0x7F, (byte) 'E', (byte) 'L', (byte) 'F'};
    /**
     * e_flags mask if executable file conforms to hardware floating-point
     * procedure-call standard (arm ABI version 5)
     */
    private static final int EF_ARM_ABI_FLOAT_HARD = 0x00000400;
    /**
     * e_flags mask if executable file conforms to software floating-point
     * procedure-call standard (arm ABI version 5)
     */
    private static final int EF_ARM_ABI_FLOAT_SOFT = 0x00000200;
    private static final int EI_DATA_BIG_ENDIAN = 2;
    private static final int E_MACHINE_ARM = 0x28;
    private static final int EI_CLASS_64BIT = 2;
    
    public static ELFAnalyser analyse(String filename) throws IOException {
        ELFAnalyser res = new ELFAnalyser(filename);
        res.runDetection();
        return res;
    }
    
    private final String filename;
    private boolean ELF = false;
    private boolean _64Bit = false;
    private boolean bigEndian = false;
    private boolean armHardFloat = false;
    private boolean armSoftFloat = false;
    private boolean arm = false;

    /**
     * @return true if the parsed file was detected to be an ELF file
     */
    public boolean isELF() {
        return ELF;
    }

    /**
     * @return true if the parsed file was detected to be for a 64bit architecture
     * and pointers are expected to be 8byte wide
     */
    public boolean is64Bit() {
        return _64Bit;
    }

    /**
     * @return true if the parsed file is detected to be big endian, false if 
     * the file is little endian
     */
    public boolean isBigEndian() {
        return bigEndian;
    }

    /**
     * @return filename of the parsed file
     */
    public String getFilename() {
        return filename;
    }

    /**
     * @return true if file was detected to conform to the hardware floating-point
     * procedure-call standard
     */
    public boolean isArmHardFloat() {
        return armHardFloat;
    }

    /**
     * @return true if file was detected to conform to the software floating-point
     * procedure-call standard
     */
    public boolean isArmSoftFloat() {
        return armSoftFloat;
    }

    /**
     * @return true if the parsed file was detected to be build for the arm
     * architecture
     */
    public boolean isArm() {
        return arm;
    }

    private ELFAnalyser(String filename) {
        this.filename = filename;
    }

    private void runDetection() throws IOException {
        RandomAccessFile raf = new RandomAccessFile(filename, "r");
        try {
            // run precheck - only of if the file at least hold an ELF header parsing
            // runs further.
            if (raf.length() > 4) {
                byte[] magic = new byte[4];
                raf.seek(0);
                raf.read(magic);
                if (Arrays.equals(magic, ELF_MAGIC)) {
                    ELF = true;
                }
            }
            if (!ELF) {
                return;
            }
            raf.seek(4);
            // The total header size depends on the pointer size of the platform
            // so before the header is loaded the pointer size has to be determined
            byte sizeIndicator = raf.readByte();
            _64Bit = sizeIndicator == EI_CLASS_64BIT;
            raf.seek(0);
            ByteBuffer headerData = ByteBuffer.allocate(_64Bit ? 64 : 52);
            raf.getChannel().read(headerData, 0);
            bigEndian = headerData.get(5) == EI_DATA_BIG_ENDIAN;
            headerData.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);

            arm = headerData.get(0x12) == E_MACHINE_ARM;

            if(arm) {
                int flags = headerData.getInt(_64Bit ? 0x30 : 0x24);
                armHardFloat = (flags & EF_ARM_ABI_FLOAT_HARD) == EF_ARM_ABI_FLOAT_HARD;
                armSoftFloat = !armHardFloat;
            }
        } finally {
            try {
                raf.close();
            } catch (IOException ex) {
                // Swallow - closing 
            }
        }
    }
}
