/***************************************************************************
    begin       : Mon Mar 01 2004
    copyright   : (C) 2019 by Martin Preuss
    email       : martin@libchipcard.de

 ***************************************************************************
 *          Please see toplevel file COPYING for license details           *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif


#include "r_upload_l.h"

#include "aqebics/aqebics_l.h"
#include "aqebics/msg/msg.h"
#include "aqebics/msg/keys.h"
#include "aqebics/msg/zip.h"
#include "aqebics/msg/xml.h"
#include "aqebics/client/user_l.h"
#include "aqebics/client/provider_l.h"

#include <gwenhywfar/base64.h>
#include <gwenhywfar/gui.h>
#include <gwenhywfar/httpsession.h>
#include <gwenhywfar/cryptkeysym.h>



/* -------------------------------------------------------------------------------------------------------------------------
 * forward declarations
 * -------------------------------------------------------------------------------------------------------------------------
 */

static int _mkUploadInitRequest(AB_PROVIDER *pro,
                                GWEN_HTTP_SESSION *sess,
                                AB_USER *u,
                                const char *requestType,
                                GWEN_CRYPT_KEY *skey,
                                const char *pEu,
                                uint32_t dlen,
                                EB_MSG **pMsg);

static int _mkUploadTransferRequest(AB_PROVIDER *pro,
                                    GWEN_HTTP_SESSION *sess,
                                    AB_USER *u,
                                    const char *transactionId,
                                    const char *pData,
                                    uint32_t lData,
                                    int segmentNumber,
                                    int isLast,
                                    EB_MSG **pMsg);




/* -------------------------------------------------------------------------------------------------------------------------
 * code
 * --------------------------------------------------------------------------------------------------------------------------
 */


int EBC_Provider_XchgUploadRequest_H003(AB_PROVIDER *pro,
                                        GWEN_HTTP_SESSION *sess,
                                        AB_USER *u,
                                        const char *requestType,
                                        const uint8_t *pData,
                                        uint32_t lData)
{
  int rv;
  GWEN_CRYPT_KEY *skey;
  GWEN_BUFFER *euBuf=NULL;
  GWEN_BUFFER *dbuf;
  EB_MSG *msg=NULL;
  EB_MSG *mRsp;
  uint32_t numSegs;
  uint32_t i;
  EB_RC rc;
  GWEN_BUFFER *logbuf;

  logbuf=GWEN_Buffer_new(0, 128, 0, 1);

  /* generate session key (for now only E002 is possible) */
  DBG_INFO(AQEBICS_LOGDOMAIN, "Generating session key");
  skey=GWEN_Crypt_KeyAes128_Generate(GWEN_Crypt_CryptMode_Cbc, 16, 2);
  if (skey==NULL) {
    DBG_ERROR(AQEBICS_LOGDOMAIN, "Unable to generate AES-128 key");
    return GWEN_ERROR_GENERIC;
  }

  if (!(EBC_User_GetFlags(u) & EBC_USER_FLAGS_NO_EU)) {
    /* generate electronic signature */
    DBG_INFO(AQEBICS_LOGDOMAIN, "Generating electronic signature for user [%s]", AB_User_GetUserId(u));
    euBuf=GWEN_Buffer_new(0, 1024, 0, 1);
    rv=EBC_Provider_MkEuCryptZipDoc(pro, u, requestType, pData, lData, skey, euBuf);
    if (rv<0) {
      DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
      GWEN_Buffer_free(euBuf);
      GWEN_Crypt_Key_free(skey);
      GWEN_Buffer_AppendString(logbuf, I18N("\tError signing upload document"));
      GWEN_Buffer_AppendString(logbuf, " (");
      GWEN_Buffer_AppendString(logbuf, AB_User_GetUserId(u));
      GWEN_Buffer_AppendString(logbuf, ")\n");
      Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
      GWEN_Buffer_free(logbuf);
      return rv;
    }
    GWEN_Buffer_AppendString(logbuf, I18N("\tUpload document signed"));
    GWEN_Buffer_AppendString(logbuf, " (");
    GWEN_Buffer_AppendString(logbuf, AB_User_GetUserId(u));
    GWEN_Buffer_AppendString(logbuf, ")\n");
  }

  /* encrypt and encode data */
  DBG_INFO(AQEBICS_LOGDOMAIN, "Encrypting, zipping and encoding upload data");
  dbuf=GWEN_Buffer_new(0, (lData*4)/3, 0, 1);
  rv=EBC_Provider_EncryptData(pro, u, skey, pData, lData, dbuf);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    GWEN_Buffer_free(dbuf);
    GWEN_Buffer_free(euBuf);
    GWEN_Crypt_Key_free(skey);
    GWEN_Buffer_AppendString(logbuf, I18N("\tError encrypting upload document\n"));
    Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
    GWEN_Buffer_free(logbuf);
    return rv;
  }
  GWEN_Buffer_AppendString(logbuf, I18N("\tUpload document encrypted\n"));

  numSegs=(GWEN_Buffer_GetUsedBytes(dbuf)+(1024*1024)-1)/(1024*1024);

  /* create upload init request */
  DBG_INFO(AQEBICS_LOGDOMAIN, "Generating upload init request");
  if (EBC_User_GetFlags(u) & EBC_USER_FLAGS_NO_EU)
    rv=_mkUploadInitRequest(pro, sess, u, requestType, skey, NULL, GWEN_Buffer_GetUsedBytes(dbuf), &msg);
  else
    rv=_mkUploadInitRequest(pro, sess, u, requestType, skey, GWEN_Buffer_GetStart(euBuf), GWEN_Buffer_GetUsedBytes(dbuf),
                            &msg);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    GWEN_Buffer_free(dbuf);
    GWEN_Buffer_free(euBuf);
    GWEN_Crypt_Key_free(skey);
    Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
    GWEN_Buffer_free(logbuf);
    return rv;
  }

  /* exchange requests */
  DBG_INFO(AQEBICS_LOGDOMAIN, "Exchanging upload init request");
  GWEN_Buffer_AppendString(logbuf, I18N("\tExchanging upload init request"));
  rv=EBC_Dialog_ExchangeMessages(sess, msg, &mRsp);
  if (rv<0 || rv>=300) {
    DBG_ERROR(AQEBICS_LOGDOMAIN, "Error exchanging messages (%d)", rv);
    EB_Msg_free(msg);
    GWEN_Buffer_free(dbuf);
    GWEN_Buffer_free(euBuf);
    GWEN_Crypt_Key_free(skey);
    Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
    GWEN_Buffer_free(logbuf);
    return rv;
  }
  EB_Msg_free(msg);
  GWEN_Buffer_free(euBuf);
  GWEN_Crypt_Key_free(skey);

  /* check response */
  assert(mRsp);

  /* log results */
  EBC_Provider_LogRequestResults(pro, mRsp, logbuf);

  rc=EB_Msg_GetResultCode(mRsp);
  if ((rc & 0xff0000)==0x090000 ||
      (rc & 0xff0000)==0x060000) {
    DBG_ERROR(AQEBICS_LOGDOMAIN, "Error response: (%06x)", rc);
    EB_Msg_free(mRsp);
    return AB_ERROR_SECURITY;
  }
  rc=EB_Msg_GetBodyResultCode(mRsp);
  if (rc) {
    if ((rc & 0xff0000)==0x090000 ||
        (rc & 0xff0000)==0x060000) {
      DBG_ERROR(AQEBICS_LOGDOMAIN, "Error response: (%06x)", rc);
      EB_Msg_free(mRsp);
      GWEN_Buffer_free(dbuf);
      Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
      GWEN_Buffer_free(logbuf);
      if ((rc & 0xfff00)==0x091300 ||
          (rc & 0xfff00)==0x091200)
        return AB_ERROR_SECURITY;
      else
        return GWEN_ERROR_GENERIC;
    }
  }
  if (1) {
    const char *s;
    char transactionId[36];
    const char *p;
    uint32_t bytesLeft;

    /* extract transaction id */
    s=EB_Msg_GetCharValue(mRsp, "header/static/TransactionID", NULL);
    if (s==NULL) {
      DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
      EB_Msg_free(mRsp);
      GWEN_Buffer_free(dbuf);
      Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
      GWEN_Buffer_free(logbuf);
      return rv;
    }
    strncpy(transactionId, s, sizeof(transactionId)-1);
    EB_Msg_free(mRsp);

    /* write data */
    p=GWEN_Buffer_GetStart(dbuf);
    bytesLeft=GWEN_Buffer_GetUsedBytes(dbuf);
    for (i=0; i<numSegs; i++) {
      uint32_t n;

      n=1024*1024;
      if (n>bytesLeft)
        n=bytesLeft;
      assert(n);
      DBG_INFO(AQEBICS_LOGDOMAIN, "Generating upload transfer request");
      rv=_mkUploadTransferRequest(pro, sess, u, transactionId, p, n, i+1, (i==numSegs-1)?1:0, &msg);
      if (rv<0) {
        DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
        GWEN_Buffer_free(dbuf);
        Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
        GWEN_Buffer_free(logbuf);
        return rv;
      }

      /* exchange requests */
      DBG_INFO(AQEBICS_LOGDOMAIN, "Exchanging upload transfer request");
      GWEN_Buffer_AppendString(logbuf, I18N("\tExchanging upload transfer request"));
      rv=EBC_Dialog_ExchangeMessages(sess, msg, &mRsp);
      if (rv<0 || rv>=300) {
        DBG_ERROR(AQEBICS_LOGDOMAIN, "Error exchanging messages (%d)", rv);
        EB_Msg_free(msg);
        GWEN_Buffer_free(dbuf);
        Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
        GWEN_Buffer_free(logbuf);
        return rv;
      }
      EB_Msg_free(msg);

      /* check response */
      assert(mRsp);

      /* log results */
      EBC_Provider_LogRequestResults(pro, mRsp, logbuf);

      rc=EB_Msg_GetResultCode(mRsp);
      if ((rc & 0xff0000)==0x090000 ||
          (rc & 0xff0000)==0x060000) {
        DBG_ERROR(AQEBICS_LOGDOMAIN, "Error response: (%06x)", rc);
        EB_Msg_free(mRsp);
        GWEN_Buffer_free(dbuf);
        Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
        GWEN_Buffer_free(logbuf);
        return AB_ERROR_SECURITY;
      }

      /* prepare next round */
      EB_Msg_free(mRsp);
      p+=n;
      bytesLeft-=n;
    } /* for */
  }

  GWEN_Buffer_free(dbuf);
  DBG_INFO(AQEBICS_LOGDOMAIN, "Upload finished");
  GWEN_Buffer_AppendString(logbuf, I18N("\tUpload finished"));
  Ab_HttpSession_AddLog(sess, GWEN_Buffer_GetStart(logbuf));
  GWEN_Buffer_free(logbuf);

  return 0;
}





int _mkUploadInitRequest(AB_PROVIDER *pro,
                         GWEN_HTTP_SESSION *sess,
                         AB_USER *u,
                         const char *requestType,
                         GWEN_CRYPT_KEY *skey,
                         const char *pEu,
                         uint32_t dlen,
                         EB_MSG **pMsg)
{
  int rv;
  EB_MSG *msg;
  const char *userId;
  const char *partnerId;
  xmlDocPtr doc;
  xmlNodePtr root_node = NULL;
  xmlNodePtr node = NULL;
  xmlNodePtr nodeX = NULL;
  xmlNodePtr nodeXX = NULL;
  /*xmlNodePtr nodeXXX = NULL;*/
  xmlNodePtr sigNode = NULL;
  GWEN_BUFFER *tbuf;
  const char *s;
  char numbuf[32];

  userId=AB_User_GetUserId(u);
  partnerId=AB_User_GetCustomerId(u);
  if (partnerId==NULL)
    partnerId=userId;

  /* create request */
  msg=EB_Msg_new();
  doc=EB_Msg_GetDoc(msg);
  root_node=xmlNewNode(NULL, BAD_CAST "ebicsRequest");
  xmlDocSetRootElement(doc, root_node);
  EB_Xml_Ebicsify(root_node, "H003");

  /* header */
  node=xmlNewChild(root_node, NULL, BAD_CAST "header", NULL);
  xmlNewProp(node, BAD_CAST "authenticate", BAD_CAST "true");

  nodeX=xmlNewChild(node, NULL, BAD_CAST "static", NULL);
  s=EBC_User_GetPeerId(u);
  nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "HostID", BAD_CAST(s?s:"EBICS"));

  /* generate Nonce */
  tbuf=GWEN_Buffer_new(0, 128, 0, 1);
  rv=EBC_Provider_GenerateNonce(pro, tbuf);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    GWEN_Buffer_free(tbuf);
    EB_Msg_free(msg);
    return rv;
  }
  nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "Nonce", BAD_CAST GWEN_Buffer_GetStart(tbuf));
  GWEN_Buffer_Reset(tbuf);

  /* generate timestamp */
  rv=EBC_Provider_GenerateTimeStamp(pro, u, tbuf);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    GWEN_Buffer_free(tbuf);
    EB_Msg_free(msg);
    return rv;
  }
  nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "Timestamp", BAD_CAST GWEN_Buffer_GetStart(tbuf));
  GWEN_Buffer_Reset(tbuf);

  nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "PartnerID", BAD_CAST partnerId);

  nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "UserID", BAD_CAST userId);

  /* order details */
  nodeXX=xmlNewChild(nodeX, NULL, BAD_CAST "OrderDetails", NULL);
  xmlNewTextChild(nodeXX, NULL, BAD_CAST "OrderType", BAD_CAST requestType);

  /* generate order id */
  rv=EBC_Provider_Generate_OrderId(pro, tbuf);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    GWEN_Buffer_free(tbuf);
    EB_Msg_free(msg);
    return rv;
  }
  xmlNewTextChild(nodeXX, NULL, BAD_CAST "OrderID", BAD_CAST GWEN_Buffer_GetStart(tbuf));
  GWEN_Buffer_free(tbuf);

  if (EBC_User_GetFlags(u) & EBC_USER_FLAGS_NO_EU)
    xmlNewTextChild(nodeXX, NULL, BAD_CAST "OrderAttribute", BAD_CAST "DZHNN");
  else
    xmlNewTextChild(nodeXX, NULL, BAD_CAST "OrderAttribute", BAD_CAST "OZHNN");
  xmlNewChild(nodeXX, NULL, BAD_CAST "StandardOrderParams", NULL);

  /* bank pubkey digests */
  nodeXX=xmlNewChild(nodeX, NULL, BAD_CAST "BankPubKeyDigests", NULL);
  rv=EBC_Provider_AddBankPubKeyDigests(pro, u, nodeXX);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    EB_Msg_free(msg);
    return rv;
  }

  /* security medium */
  xmlNewTextChild(nodeX, NULL, BAD_CAST "SecurityMedium", BAD_CAST "0000");

  snprintf(numbuf, sizeof(numbuf)-1, "%i", (dlen+(1024*1024)-1)/(1024*1024));
  numbuf[sizeof(numbuf)-1]=0;
  xmlNewTextChild(nodeX, NULL, BAD_CAST "NumSegments", BAD_CAST numbuf);


  /* mutable */
  nodeX=xmlNewChild(node, NULL, BAD_CAST "mutable", NULL);
  xmlNewTextChild(nodeX, NULL, BAD_CAST "TransactionPhase", BAD_CAST "Initialisation");


  /* prepare signature node */
  sigNode=xmlNewChild(root_node, NULL, BAD_CAST "AuthSignature", NULL);

  /* body */
  node=xmlNewChild(root_node, NULL, BAD_CAST "body", NULL);

  /* data transfer */
  nodeX=xmlNewChild(node, NULL, BAD_CAST "DataTransfer", NULL);

  /* add session key and info */
  nodeXX=xmlNewChild(nodeX, NULL, BAD_CAST "DataEncryptionInfo", NULL);
  xmlNewProp(nodeXX, BAD_CAST "authenticate", BAD_CAST "true");
  rv=EBC_Provider_FillDataEncryptionInfoNode(pro, u, skey, nodeXX);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    EB_Msg_free(msg);
    return rv;
  }

  if (!(EBC_User_GetFlags(u) & EBC_USER_FLAGS_NO_EU)) {
    /* add EU */
    nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "SignatureData", BAD_CAST pEu);
    xmlNewProp(nodeXX, BAD_CAST "authenticate", BAD_CAST "true");
  }

  /* sign */
  rv=EBC_Provider_SignMessage(pro, msg, u, sigNode);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    EB_Msg_free(msg);
    return rv;
  }

  *pMsg=msg;
  return 0;
}



int _mkUploadTransferRequest(AB_PROVIDER *pro,
                             GWEN_HTTP_SESSION *sess,
                             AB_USER *u,
                             const char *transactionId,
                             const char *pData,
                             uint32_t lData,
                             int segmentNumber,
                             int isLast,
                             EB_MSG **pMsg)
{
  int rv;
  EB_MSG *msg;
  const char *userId;
  const char *partnerId;
  xmlDocPtr doc;
  xmlNodePtr root_node = NULL;
  xmlNodePtr node = NULL;
  xmlNodePtr nodeX = NULL;
  xmlNodePtr nodeXX = NULL;
  xmlNodePtr sigNode = NULL;
  GWEN_BUFFER *tbuf;
  const char *s;
  char numbuf[32];

  userId=AB_User_GetUserId(u);
  partnerId=AB_User_GetCustomerId(u);
  if (partnerId==NULL)
    partnerId=userId;

  /* create request */
  msg=EB_Msg_new();
  doc=EB_Msg_GetDoc(msg);
  root_node=xmlNewNode(NULL, BAD_CAST "ebicsRequest");
  xmlDocSetRootElement(doc, root_node);
  EB_Xml_Ebicsify(root_node, "H003");

  /* header */
  node=xmlNewChild(root_node, NULL, BAD_CAST "header", NULL);
  xmlNewProp(node, BAD_CAST "authenticate", BAD_CAST "true");

  nodeX=xmlNewChild(node, NULL, BAD_CAST "static", NULL);
  s=EBC_User_GetPeerId(u);
  nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "HostID", BAD_CAST(s?s:"EBICS"));
  nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "TransactionID", BAD_CAST transactionId);

  /* mutable */
  nodeX=xmlNewChild(node, NULL, BAD_CAST "mutable", NULL);
  xmlNewTextChild(nodeX, NULL, BAD_CAST "TransactionPhase", BAD_CAST "Transfer");
  snprintf(numbuf, sizeof(numbuf)-1, "%d", segmentNumber);
  numbuf[sizeof(numbuf)-1]=0;
  nodeXX=xmlNewTextChild(nodeX, NULL, BAD_CAST "SegmentNumber", BAD_CAST numbuf);
  xmlNewProp(nodeXX, BAD_CAST "lastSegment", BAD_CAST(isLast?"true":"false"));

  /* prepare signature node */
  sigNode=xmlNewChild(root_node, NULL, BAD_CAST "AuthSignature", NULL);

  /* body */
  node=xmlNewChild(root_node, NULL, BAD_CAST "body", NULL);

  /* data transfer */
  nodeX=xmlNewChild(node, NULL, BAD_CAST "DataTransfer", NULL);
  tbuf=GWEN_Buffer_new(0, lData, 0, 1);
  GWEN_Buffer_AppendBytes(tbuf, pData, lData);
  nodeXX=xmlNewChild(nodeX, NULL, BAD_CAST "OrderData", BAD_CAST GWEN_Buffer_GetStart(tbuf));
  GWEN_Buffer_free(tbuf);

  /* sign */
  rv=EBC_Provider_SignMessage(pro, msg, u, sigNode);
  if (rv<0) {
    DBG_INFO(AQEBICS_LOGDOMAIN, "here (%d)", rv);
    EB_Msg_free(msg);
    return rv;
  }

  *pMsg=msg;
  return 0;
}








