"""
Executable and Linkable Format (ELF), 32 bit, big or little endian
Used on *nix systems as a replacement of the older a.out format

Big-endian support kindly submitted by Craig McQueen (mcqueen-c#edsrd1!yzk!co!jp)
"""
from construct import *
from construct.lib.py3compat import b


def elf32_body(ElfInt16, ElfInt32):
    elf32_program_header = Struct("program_header",
                                  Enum(ElfInt32("type"),
                                       NULL=0,
                                       LOAD=1,
                                       DYNAMIC=2,
                                       INTERP=3,
                                       NOTE=4,
                                       SHLIB=5,
                                       PHDR=6,
                                       _default_=Pass,
                                       ),
                                  ElfInt32("offset"),
                                  ElfInt32("vaddr"),
                                  ElfInt32("paddr"),
                                  ElfInt32("file_size"),
                                  ElfInt32("mem_size"),
                                  ElfInt32("flags"),
                                  ElfInt32("align"),
                                  )

    elf32_section_header = Struct("section_header",
                                  ElfInt32("name_offset"),
                                  Pointer(lambda ctx: ctx._.strtab_data_offset + ctx.name_offset,
                                          CString("name")
                                          ),
                                  Enum(ElfInt32("type"),
                                       NULL=0,
                                       PROGBITS=1,
                                       SYMTAB=2,
                                       STRTAB=3,
                                       RELA=4,
                                       HASH=5,
                                       DYNAMIC=6,
                                       NOTE=7,
                                       NOBITS=8,
                                       REL=9,
                                       SHLIB=10,
                                       DYNSYM=11,
                                       _default_=Pass,
                                       ),
                                  ElfInt32("flags"),
                                  ElfInt32("addr"),
                                  ElfInt32("offset"),
                                  ElfInt32("size"),
                                  ElfInt32("link"),
                                  ElfInt32("info"),
                                  ElfInt32("align"),
                                  ElfInt32("entry_size"),
                                  OnDemandPointer(lambda ctx: ctx.offset,
                                                  HexDumpAdapter(Field("data", lambda ctx: ctx.size))
                                                  ),
                                  )

    return Struct("body",
                  Enum(ElfInt16("type"),
                       NONE=0,
                       RELOCATABLE=1,
                       EXECUTABLE=2,
                       SHARED=3,
                       CORE=4,
                       ),
                  Enum(ElfInt16("machine"),
                       NONE=0,
                       M32=1,
                       SPARC=2,
                       I386=3,
                       Motorolla68K=4,
                       Motorolla88K=5,
                       Intel860=7,
                       MIPS=8,
                       _default_=Pass
                       ),
                  ElfInt32("version"),
                  ElfInt32("entry"),
                  ElfInt32("ph_offset"),
                  ElfInt32("sh_offset"),
                  ElfInt32("flags"),
                  ElfInt16("header_size"),
                  ElfInt16("ph_entry_size"),
                  ElfInt16("ph_count"),
                  ElfInt16("sh_entry_size"),
                  ElfInt16("sh_count"),
                  ElfInt16("strtab_section_index"),

                  # calculate the string table data offset (pointer arithmetics)
                  # ugh... anyway, we need it in order to read the section names, later on
                  Pointer(lambda ctx:
                          ctx.sh_offset + ctx.strtab_section_index * ctx.sh_entry_size + 16,
                          ElfInt32("strtab_data_offset"),
                          ),

                  # program header table
                  Rename("program_table",
                         Pointer(lambda ctx: ctx.ph_offset,
                                 Array(lambda ctx: ctx.ph_count,
                                       elf32_program_header
                                       )
                                 )
                         ),

                  # section table
                  Rename("sections",
                         Pointer(lambda ctx: ctx.sh_offset,
                                 Array(lambda ctx: ctx.sh_count,
                                       elf32_section_header
                                       )
                                 )
                         ),
                  )


elf32_body_little_endian = elf32_body(ULInt16, ULInt32)
elf32_body_big_endian = elf32_body(UBInt16, UBInt32)

elf32_file = Struct("elf32_file",
                    Struct("identifier",
                           Magic(b("\x7fELF")),
                           Enum(Byte("file_class"),
                                NONE=0,
                                CLASS32=1,
                                CLASS64=2,
                                ),
                           Enum(Byte("encoding"),
                                NONE=0,
                                LSB=1,
                                MSB=2,
                                ),
                           Byte("version"),
                           Padding(9),
                           ),
                    Embedded(IfThenElse("body", lambda ctx: ctx.identifier.encoding == "LSB",
                                        elf32_body_little_endian,
                                        elf32_body_big_endian,
                                        )),
                    )

if __name__ == "__main__":
    obj = elf32_file.parse_stream(open("../../../tests/_ctypes_test.so", "rb"))
    # [s.data.value for s in obj.sections]
    print(obj)
