import io
import pathlib
import struct

import pytest

from packaging._elffile import EIClass, EIData, ELFFile, ELFInvalid, EMachine

DIR_MANYLINUX = pathlib.Path(__file__, "..", "manylinux").resolve()
DIR_MUSLLINUX = pathlib.Path(__file__, "..", "musllinux").resolve()
BIN_MUSL_X86_64 = DIR_MUSLLINUX.joinpath("musl-x86_64").read_bytes()


@pytest.mark.parametrize(
    ("name", "capacity", "encoding", "machine"),
    [
        ("x86_64-x32", EIClass.C32, EIData.Lsb, EMachine.X8664),
        ("x86_64-i386", EIClass.C32, EIData.Lsb, EMachine.I386),
        ("x86_64-amd64", EIClass.C64, EIData.Lsb, EMachine.X8664),
        ("armv7l-armel", EIClass.C32, EIData.Lsb, EMachine.Arm),
        ("armv7l-armhf", EIClass.C32, EIData.Lsb, EMachine.Arm),
        ("s390x-s390x", EIClass.C64, EIData.Msb, EMachine.S390),
    ],
)
def test_elffile_glibc(
    name: str, capacity: EIClass, encoding: EIData, machine: EMachine
) -> None:
    path = DIR_MANYLINUX.joinpath(f"hello-world-{name}")
    with path.open("rb") as f:
        ef = ELFFile(f)
        assert ef.capacity == capacity
        assert ef.encoding == encoding
        assert ef.machine == machine
        assert ef.flags is not None


@pytest.mark.parametrize(
    ("name", "capacity", "encoding", "machine", "interpreter"),
    [
        (
            "aarch64",
            EIClass.C64,
            EIData.Lsb,
            EMachine.AArch64,
            "aarch64",
        ),
        ("i386", EIClass.C32, EIData.Lsb, EMachine.I386, "i386"),
        ("x86_64", EIClass.C64, EIData.Lsb, EMachine.X8664, "x86_64"),
    ],
)
def test_elffile_musl(
    name: str, capacity: EIClass, encoding: EIData, machine: EMachine, interpreter: str
) -> None:
    path = DIR_MUSLLINUX.joinpath(f"musl-{name}")
    with path.open("rb") as f:
        ef = ELFFile(f)
        assert ef.capacity == capacity
        assert ef.encoding == encoding
        assert ef.machine == machine
        assert ef.interpreter == f"/lib/ld-musl-{interpreter}.so.1"


@pytest.mark.parametrize(
    "data",
    [
        # Too short for magic.
        b"\0",
        # Enough for magic, but not ELF.
        b"#!/bin/bash" + b"\0" * 16,
        # ELF, but unknown byte declaration.
        b"\x7fELF\3" + b"\0" * 16,
    ],
    ids=["no-magic", "wrong-magic", "unknown-format"],
)
def test_elffile_bad_ident(data: bytes) -> None:
    with pytest.raises(ELFInvalid):
        ELFFile(io.BytesIO(data))


def test_elffile_no_section() -> None:
    """Enough for magic, but not the section definitions."""
    data = BIN_MUSL_X86_64[:25]
    with pytest.raises(ELFInvalid):
        ELFFile(io.BytesIO(data))


def test_elffile_invalid_section() -> None:
    """Enough for section definitions, but not the actual sections."""
    data = BIN_MUSL_X86_64[:58]
    assert ELFFile(io.BytesIO(data)).interpreter is None


def test_elffle_no_interpreter_section() -> None:
    ef = ELFFile(io.BytesIO(BIN_MUSL_X86_64))

    # Change all sections to *not* PT_INTERP.
    data = BIN_MUSL_X86_64
    for i in range(ef._e_phnum + 1):
        sb = ef._e_phoff + ef._e_phentsize * i
        se = sb + ef._e_phentsize
        section = struct.unpack(ef._p_fmt, data[sb:se])
        data = data[:sb] + struct.pack(ef._p_fmt, 0, *section[1:]) + data[se:]

    assert ELFFile(io.BytesIO(data)).interpreter is None
