#include "binaryverifier.h"

#include <QFile>
#include <stdio.h>

#include "elf32.h"

#include <string.h>

//++
typedef char g_sign_string16_t[16];
typedef char g_sign_string32_t[32];
typedef char g_sign_string64_t[64];
typedef char g_sign_string128_t[128];
typedef char g_sign_string256_t[256];
typedef char g_sign_string512_t[256];

typedef uint8_t g_sign_id_t[16];  /*exactly 8 chars*/

struct g_sign_static_data_t
{
  g_sign_string16_t nickname;
  g_sign_string16_t vstr;
  g_sign_string32_t pn;
  g_sign_string32_t fullname;
  g_sign_string64_t info;
};

struct g_sign_static_t
{
  g_sign_id_t marker_begin;  /* '<<GS++>>' */
  g_sign_id_t signversion;

  g_sign_static_data_t data;

  uint32_t dummy_signversion_align;
  g_sign_id_t marker_end;  /* '<<GS-->>' */
};

#define G_SIGN_STATIC_BEGIN(INAME_) const struct g_sign_static_t INAME_={ "<<GS+>>@!#!", "0.0",{
#define G_SIGN_STATIC_END   }, 0, "<<GS->>@!#!"}

#define G_SIGN_STR_VER2(vmajor_, vminor_)    #vmajor_ "." #vminor_
#define G_SIGN_STR_VER3(vmajor_, vminor_, vbrach_)    #vmajor_ "." #vminor_ "." ##vbrach_


struct g_sign_extern_data_t
{
  g_sign_string64_t fullname;
  g_sign_string64_t filename;

  g_sign_string16_t buildid;

  g_sign_string64_t date;

  g_sign_string64_t host;

  g_sign_string64_t md5_hash_str;
  g_sign_string16_t md5_hash_bin;

  g_sign_string512_t cms_signature; /*from CMS, that is SVN or GIT or what else*/

  g_sign_string512_t userinfo;
};

struct g_sign_extern_t
{
  g_sign_id_t marker_begin; /* '((GS++))' */
  g_sign_id_t signversion;

  g_sign_extern_data_t data;

  uint32_t dummy_signversion_align;
  g_sign_id_t marker_end; /* '((GS--))' */
};
//--

class BinaryVerifier::Implementation
{
public:
    QString errorString;
    BinaryInfo info;

    Implementation()
    {
    }

};

BinaryVerifier::BinaryVerifier():
    p_(*new Implementation)
{

}

BinaryVerifier::~BinaryVerifier()
{
    delete &p_;
}

BinaryInfo BinaryVerifier::info() const
{
    return p_.info;
}

bool BinaryVerifier::verify(const QString &filename)
{
    QFile f(filename);
    bool ok=f.open(QIODevice::ReadOnly);

    if (!ok)
    {
        p_.errorString=f.errorString();
        return false;
    }

    QByteArray d=f.readAll();
    return verify(d);
}

bool BinaryVerifier::verify(const QByteArray& d)
{
    Elf32_Ehdr h;
    qint64 r=d.size(); //f.read((char*)&h, sizeof h);
    if (r<sizeof h)
    {
        p_.errorString="Reading header";
        return false;
    }

    h=*(Elf32_Ehdr*)d.constData();

    if (!((h.e_ident[0]==ELFMAG0) || (h.e_ident[1]==ELFMAG1) || (h.e_ident[2]==ELFMAG2) || (h.e_ident[3]==ELFMAG3)))
    {
        p_.info.m_ftm=BinaryInfo::Binary;
        p_.info.m_fmtName="Binary";
        //A Xilinx bitstream?
        return true;
    }
    p_.info.m_ftm=BinaryInfo::ELF;
    p_.info.m_fmtName="ELF";

    if (h.e_ident[EI_DATA]!=ELFDATA2LSB)
    {
        p_.errorString="Big Endian";
        return false;
    }
    if (h.e_ident[EI_CLASS]!=ELFCLASS32)
    {
        p_.errorString="64bits";
        return false;
    }

    if (h.e_type!=ET_EXEC)
    {
        p_.errorString="not ELF EXEC";
        return false;
    }

    switch(h.e_machine)
    {
    case EM_TI_C6000:
        p_.info.m_arch=BinaryInfo::C6000;
        p_.info.m_archName="C6000";
        p_.info.m_pn="TMS320C6XXX";
        p_.info.m_partType="XMC-DSP";
        break;
    case EM_ARM:
        p_.info.m_arch=BinaryInfo::ARM;
        p_.info.m_archName="ARM";
        p_.info.m_partType="XMC-ZynQ(?)";
        break;
    default:
        break;
    }


    //QByteArray d=f.readAll();

    p_.info.m_hasInfo=false;

    {
        int spos=0;
        int epos=0;

        QString markerBegin("<<GS+>>");
        QString markerEnd("<<GS->>");
        int expected_size=sizeof(g_sign_static_t)-sizeof(g_sign_id_t);

        do
        {
          spos=d.indexOf(markerBegin, spos+1);
          epos=d.indexOf(markerEnd, spos);
          int size=epos-spos;
          if ((spos>=0) || (epos>spos))
          {
            if (size>=expected_size)
            {
              g_sign_static_t s=*reinterpret_cast<const g_sign_static_t*>(&(d.data()[spos]));
              p_.info.m_hasInfo=true;
              p_.info.m_name=s.data.fullname;
              p_.info.m_usr=s.data.info;
              p_.info.m_ibc=s.data.pn;
              p_.info.m_version=s.data.vstr;
              break;
            }
          }
        } while(spos>=0);
    }

    {
        int spos=0;
        int epos=0;

        QString markerBegin("<<GX+>>");
        QString markerEnd("<<GX->>");
        int expected_size=sizeof(g_sign_extern_t)-sizeof(g_sign_id_t);

        do
        {
          spos=d.indexOf(markerBegin, spos+1);
          epos=d.indexOf(markerEnd, spos);
          int size=epos-spos;
          if ((spos>=0) || (epos>spos))
          {
            if (size>=expected_size)
            {
              g_sign_extern_t s=*reinterpret_cast<const g_sign_extern_t*>(&(d.data()[spos]));
              p_.info.m_hasInfo=true;
              //p_.info.m_name=s.data.fullname;
              p_.info.m_host=s.data.host;
              p_.info.m_bid=s.data.buildid;
              p_.info.m_md5=s.data.md5_hash_str;
              //p_.info.m_usr=s.data.userinfo;
              p_.info.m_date=s.data.date;
              break;
            }
          }
        } while(spos>=0);
    }

    return true;
}
